一、引出問題
很多小夥伴使用Spring事務時,爲了省事都喜歡使用@Transactional。但是@Transactional配合鎖,會導致一些預期之外的問題!
在此舉例說明。
1、資料準備
我們將在該表中,實現level資料遞減的併發操作。
Controller中,簡單模擬10個執行緒各自執行10次:
二、@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,說明存在併發問題!
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(); // 解鎖 } }
執行結果:我們發現,使用鎖是可以控制併發問題。
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之後,鎖怎麼就失效了呢!
4、問題分析
我們都知道,@Transactional是透過使用AOP,在目標方法執行前後進行事務的開啟和提交。所以,Lock鎖住的程式碼,其實並沒有包含住一整個事務!
透過下面的圖理解一下:
當執行緒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(); // 解鎖 } }
執行結果:沒有併發問題出現!
或者直接在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(); // 解鎖 } }
執行結果:我們發現,將整個事務都鎖住,就沒問題了!
來源:juejin.cn/post/7311603432925102095