記 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的鎖.代理

相關文章
相關標籤/搜索