當前位置: 妍妍網 > 碼農

@Transactional 中使用執行緒鎖導致了鎖失效,震驚了...

2024-05-25碼農

點選「 IT碼徒 」, 關註,置頂 公眾號

每日技術幹貨,第一時間送達!

1

引出問題

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

在此舉例說明。

1、數據準備

我們將在該表中,實作level數據遞減的並行操作。

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

2

@Transactional是如何導致鎖失效的

// service程式碼
publicvoidtest(){
// 簡單的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();
publicvoidtest() {
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
publicvoidtest()
{
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,所以出現了並行問題。

3

解決方案

1、@Transactional單獨一個方法

private Lock lock = new ReentrantLock();
@Transactional
publicvoidtest1()
{
// 簡單的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;
publicvoidtest() {
try {
// 加鎖
lock.lock();
// 自己註入自己,以使用到其代理類
commonService.test1();
} finally {
lock.unlock(); // 解鎖
}
}

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

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

2、使用編程式事務

// service程式碼
private Lock lock = new ReentrantLock();
@Autowired
private PlatformTransactionManager transactionManager;
publicvoidtest() {
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

END

PS:防止找不到本篇文章,可以收藏點贊,方便翻閱尋找哦。

往期推薦