记 synchronized 与 ReentrantLock 在spring 事务中失效了

导读:
最近发现某个项目的用户流水和帐款金额出现了并发问题, 而后使用乐观锁解决了这个问题, 可是由于有跑批任务 在同一时刻 同一用户的帐款 会增长多条流水因而就出现:redis

  • StaleObjectStateException,
  • ObjectOptimisticLockingFailureException,
  • CannotAcquireLockException

的异常, 虽然 帐款的问题是解决了 (单笔没问题后面的批处理仍是会把失败的流水继续推过来) 但仍是不是一个比较好且简单的解, 开动脑筋,在不改动主要业务逻辑的状况下如何对其打补丁呢?
最简单的方式就是将并行改成串行 而后就在数据库查询修改的方法外面加了synchronized 关键字, 但加上以后并行查询的数据仍是老数据 synchronized 竟然失效了, 不该该啊, 而后想到了应该是事务未提交致使的,可是方法上有事务注解啊, 接着想到了 事务是经过动态代理实现的显然动态代理的方法并无synchronized关键字修饰.
这是一个例子:spring

@Service
class WalletApplicationSerive{

    @Transactional(rollbackFor =  Exception.class)
    public synchronized void pay(accountId, amount, outerCode){
       // 数据库查询以后修改
    }
}

当咱们使用事务的时候 其背后的实现是动态代理结合IOC容器获取出来的WalletApplicationSerive 的实例已经被Spring 换成了(spring 实现的更复杂, 为了方便理解这里以静态代理为例 ,这只是一个简单的示例)数据库

class WalletApplicationSeriveProxy{

   private WalletApplicationSerivce tagert;
   
    public  void pay(accountId, amount, outerCode){
       tx.begin()
       try{
          tagert.pay(accountId, amount, outerCode)
       }catch(Exception e){
          tx.rollback()
          throw e;
       }
       tx.commit()
      
    }
}

动态代理:并发

// 目标对象
    Object target ;

    Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

           // 带有@Transcational注解的方法都会被拦截
           tx.begin();
           try{
               method.invoke(target, args);
           }catch(Exception e){
              tx.rollback();
              throw e;
           }
           tx.commit();
           return null;
        }
        
    });

一切都变得简单了 这也就是synchronized与ReentrantLock在spring 事务中失效了的缘由.
要如何解决呢? 很简单 在 代理类外面增长上事务.ide

@Service
class WalletApplicationSerive{

    @Autowired
    InnerWalletApplicationSerive inner;
    
    public synchronized void pay(accountId, amount, outerCode){
       inner.pay(accountId, amount, outerCode)
    }
    
    @Service
    static class InnerWalletApplicationSerive{
        @Transactional(rollbackFor =  Exception.class)
        public void pay(accountId, amount, outerCode){
           // 数据库查询以后修改
        }
    }
}

问题解决, 但这里锁的粒度太粗了, 能够在对锁进行更细的粒度改造:ui

@Service
class WalletApplicationSerive{

    @Autowired
    InnerWalletApplicationSerive inner;
    
    public  void pay(accountId, amount, outerCode){
       synchronized(WalletLockManger.getLock(accountId)){
            inner.pay(accountId, amount, outerCode)
       }
      
    }
    
    @Service
    static class InnerWalletApplicationSerive{
        @Transactional(rollbackFor =  Exception.class)
        public void pay(accountId, amount, outerCode){
           // 数据库查询以后修改
        }
    }
}

class WalletLockManger {

    private static final Map<String, String> lockMap = new ConcurrentHashMap<>();

    public static String getLock(String accountId) {
        return lockMap.computeIfAbsent(accountId, Function.identity());
    }
}

synchronized(WalletLockManger.getLock(accountId)) 这个里有很大的改造空间, 后面若是 要部署多个实例的时候 能够将这里换成redis的锁.代理

相关文章
相关标签/搜索