切換語言為:簡體
為什麼 @Transactional 中使用執行緒鎖會導致鎖失效,以及解決方案分享

為什麼 @Transactional 中使用執行緒鎖會導致鎖失效,以及解決方案分享

  • 爱糖宝
  • 2024-05-22
  • 2090
  • 0
  • 0

一、引出問題

很多小夥伴使用Spring事務時,爲了省事都喜歡使用@Transactional。但是@Transactional配合鎖,會導致一些預期之外的問題!

在此舉例說明。

1、資料準備

我們將在該表中,實現level資料遞減的併發操作。

為什麼 @Transactional 中使用執行緒鎖會導致鎖失效,以及解決方案分享

Controller中,簡單模擬10個執行緒各自執行10次:

為什麼 @Transactional 中使用執行緒鎖會導致鎖失效,以及解決方案分享

二、@Transactional是如何導致鎖失效的

1、不加鎖

// service程式碼
public void test() {
    // 簡單的select + update 模擬業務場景
    Model model = mapper.choseOne("99");
 // 實現 level -- 操作
    Model updater = new Model();
    updater.setId("99");
    updater.setLevel(model.getLevel() - 1);
    mapper.updateOne(updater);
}

執行結果:我們發現,level只扣減了26,說明存在併發問題!

為什麼 @Transactional 中使用執行緒鎖會導致鎖失效,以及解決方案分享

2、使用鎖

// service程式碼
private Lock lock = new ReentrantLock();
public void test() {
 try {
     //加鎖
     lock.lock();
     // 簡單的select + update 模擬業務場景
     Model model = mapper.choseOne("99");
  // 實現 level -- 操作
     Model updater = new Model();
     updater.setId("99");
     updater.setLevel(model.getLevel() - 1);
     mapper.updateOne(updater);
 } finally {
       lock.unlock(); // 解鎖
    }
}

執行結果:我們發現,使用鎖是可以控制併發問題。

為什麼 @Transactional 中使用執行緒鎖會導致鎖失效,以及解決方案分享

3、使用鎖+@Transactional

// service程式碼
private Lock lock = new ReentrantLock();
@Transactional
public void test() {
 try {
     //加鎖
     lock.lock();
     // 簡單的select + update 模擬業務場景
     Model model = mapper.choseOne("99");
  // 實現 level -- 操作
     Model updater = new Model();
     updater.setId("99");
     updater.setLevel(model.getLevel() - 1);
     mapper.updateOne(updater);
 } finally {
       lock.unlock(); // 解鎖
    }
}

執行結果:我們發現,level只扣減了86!用了@Transactional之後,鎖怎麼就失效了呢!

為什麼 @Transactional 中使用執行緒鎖會導致鎖失效,以及解決方案分享

4、問題分析

我們都知道,@Transactional是透過使用AOP,在目標方法執行前後進行事務的開啟和提交。所以,Lock鎖住的程式碼,其實並沒有包含住一整個事務!

透過下面的圖理解一下:

為什麼 @Transactional 中使用執行緒鎖會導致鎖失效,以及解決方案分享

當執行緒A將level設定為99時,此時鎖已經釋放了,但是事務還沒提交!!執行緒B此時可以獲取到鎖並進行查詢,查詢出來的level還是執行緒A修改之前的100,所以出現了併發問題。

三、解決方案

1、@Transactional單獨一個方法

private Lock lock = new ReentrantLock();
@Transactional
public void test1() {
    // 簡單的select + update 模擬業務場景
    Model model = mapper.choseOne("99");
 // 實現 level -- 操作
    Model updater = new Model();
    updater.setId("99");
    updater.setLevel(model.getLevel() - 1);
    mapper.updateOne(updater);
}
@Autowired
@Lazy
private CommonService commonService;
public void test() {
    try {
        // 加鎖
        lock.lock();
        // 自己注入自己,以使用到其代理類
        commonService.test1();
    } finally {
        lock.unlock(); // 解鎖
    }
}

執行結果:沒有併發問題出現!

為什麼 @Transactional 中使用執行緒鎖會導致鎖失效,以及解決方案分享

或者直接在controller層加鎖,也是一樣的道理。

2、使用程式設計式事務

// service程式碼
private Lock lock = new ReentrantLock();
@Autowired
private PlatformTransactionManager transactionManager;
public void test() {
 try {
     //加鎖
     lock.lock();
     // 程式設計式事務
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
     // 簡單的select + update 模擬業務場景
     Model model = mapper.choseOne("99");
  // 實現 level -- 操作
     Model updater = new Model();
     updater.setId("99");
     updater.setLevel(model.getLevel() - 1);
     mapper.updateOne(updater);
  // 在鎖中提交
        transactionManager.commit(status);
 } finally {
       lock.unlock(); // 解鎖
    }
}

執行結果:我們發現,將整個事務都鎖住,就沒問題了!

為什麼 @Transactional 中使用執行緒鎖會導致鎖失效,以及解決方案分享

來源:juejin.cn/post/7311603432925102095

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.