成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

@Transactional中使用線程鎖導致了鎖失效,震驚我一整年!

開發 前端
在@Transactional注解的方法內部使用線程鎖時,由于事務管理和鎖操作在時間上的不一致,可能會導致鎖失效的問題。

今天給大家分享一個線上系統里發現的生產實踐案例,就是平時大家應該都會用@Transactional注解去實現事務是不是?因為這個注解底層說白了很簡單,就是會去代理你這個方法的執行,一旦代理了你的方法執行,其實就可以在方法執行前開一個事務,方法執行完以后如果成功就提交事務,有異常就回滾事務。

這樣就可以讓你這個方法里所有的數據庫操作匯集到一個事務里去了,這個相信大家其實都是懂的,平時 開發也都是這么做的。

那大家有沒有想過,要是我們在這個事務注解里用了多線程并發加鎖的代碼,可能會導致這個鎖失效,也就是沒法實現多線程在加鎖代碼里串行加鎖執行?這個簡直是一個巨坑,妥妥的線上生產事故案例,下面我們就開始分下這個案例。

一、@Transactional與線程鎖的基本使用

首先,我們簡要回顧一下@Transactional和線程鎖的基本用法。

1. @Transactional注解

@Transactional注解可以應用于接口定義、接口中的方法、類定義或類中的public方法上。其主要作用是聲明一個方法需要在事務環境中執行。Spring框架會在運行時通過AOP(面向切面編程)代理機制,自動管理事務的開啟、提交和回滾。

@Service
public class SomeService {


    @Transactional
    public void someTransactionalMethod() {
        // 業務邏輯
    }
}

2. 線程鎖(如ReentrantLock)

線程鎖用于控制多個線程對共享資源的并發訪問,防止數據不一致的問題。ReentrantLock是Java并發包java.util.concurrent.locks中的一個類,它提供了比synchronized關鍵字更靈活的鎖定操作。

public class SomeClass {
    private final Lock lock = new ReentrantLock();


    public void someMethod() {
        lock.lock();
        try {
            // 業務邏輯
        } finally {
            lock.unlock();
        }
    }
}

二、@Transactional中使用線程鎖導致的問題

在@Transactional注解的方法內部使用線程鎖時,可能會遇到鎖失效的問題。這是因為@Transactional通過AOP在目標方法執行前后進行事務的開啟和提交,而線程鎖則直接作用于方法內部的代碼塊。這種機制上的差異導致了事務和鎖的管理在時間上不一致,進而引發鎖失效。

示例場景

假設我們有一個服務類,其中有一個方法需要在事務環境中更新數據庫記錄,并在這個過程中使用線程鎖控制并發訪問。

@Service
public class UpdateService {


    private final Lock lock = new ReentrantLock();


    @Transactional
    public void updateData() {
        lock.lock();
        try {
            // 模擬數據庫更新操作
            System.out.println("Updating data...");
            // 假設這里有一些耗時的數據庫操作
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
    }
}

在這個例子中,雖然我們在方法內部使用了ReentrantLock來加鎖,但鎖的釋放是在事務提交之前完成的。如果在鎖釋放后、事務提交前,有其他線程進入并嘗試更新相同的數據,就可能讀取到未提交的數據,從而導致數據不一致。

因為一旦把事務和鎖放一起用,就會顯得有點詭異,你上面的代碼想的是說用事務注解控制數據庫事務,異常就回滾,成功就提交,對吧?然后你想加鎖以后就是每個線程串行執行,一個線程加鎖,更新數據庫,提交事務,釋放鎖,下一個線程過來加鎖,讀取更新數據庫,注意,這里應該是接著上一個現成的更新結果來做的,完了再提交事務,釋放鎖,對吧?

問題是,如果忽略了事務注解的工作機制,忘了那個事務控制其實是在鎖代碼外面的,因為spring會用AOP代理機制接管方法執行,事務管控是在方法執行外面的,所以很可能你開啟一共事務,然后加鎖,執行數據庫更新,接著就直接釋放鎖了,然后此時事務可能還沒提交!!!!

接著別的線程就可以進入一個方法了,此時他會開啟一個自己的事務,在mysql層面多個事務并發的時候是有自己的隔離機制的,跟你的代碼里的加鎖是沒直接關系的,此時新的線程是可以進入代碼塊拿到鎖的,畢竟你之前一個線程都釋放代碼里的鎖了!

然后新的線程執行數據庫的讀取和更新操作,其實是基于上一個線程的事務沒提交的那個臟數據在執行,所以此時就會出現數據不一致的情況,看起來就跟多個線程亂序更新數據庫一樣,跟你想的就不一樣了,對吧?

所以這就是所謂的事務注解里線程加鎖可能導致鎖沒生效,多個線程還是亂序在執行。

三、問題分析

問題的根源在于@Transactional和線程鎖的管理機制不同步。@Transactional通過AOP代理在方法執行前后進行事務操作,而線程鎖則是直接在方法內部控制并發。當方法執行完畢后,即使事務還未提交,鎖已經被釋放,這就為其他線程提供了進入并操作共享資源的機會。

四、解決方案

為了解決@Transactional中使用線程鎖導致的鎖失效問題,我們可以采用以下幾種方案:

1. 將事務管理和鎖操作分離

將需要加鎖的業務邏輯封裝到一個單獨的方法中,并在調用該方法前手動管理事務。這種方式可以避免@Transactional和線程鎖在時間上的不一致。也就是通過手動管控事務提交和回滾,跟代碼里的加鎖同步一致,避免這個問題。

按照我們的想法,說白了就是應該是在加鎖代碼里面讓事務先提交,然后再釋放鎖,這樣就可以保證多個線程對數據庫的更新是串行的。

@Service
public class UpdateService {


    private final Lock lock = new ReentrantLock();


    @Autowired
    private PlatformTransactionManager transactionManager;


    public void updateData() {
        lock.lock();
        try {
            TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
            try {
                // 模擬數據庫更新操作
                System.out.println("Updating data...");
                // 假設這里有一些耗時的數據庫操作
                Thread.sleep(1000);
                transactionManager.commit(status);
            } catch (Exception e) {
                transactionManager.rollback(status);
                throw e;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
    }
}

注意:這種方式雖然解決了鎖失效的問題,但手動管理事務會使代碼變得復雜,且容易出錯。

2. 使用@Transactional單獨一個方法

將需要事務支持的方法單獨提出來,并確保該方法不包含任何鎖操作。在調用該方法前,通過其他方式(如使用代理類或直接在調用者處)管理鎖。這個本質其實也是在鎖范圍內讓事務先執行和提交,只不過通過方法的提取避免了手動加提交事務,其實是更加的優雅的!

@Service
public class UpdateServiceImpl implements UpdateService {


    @Autowired
    @Lazy
    private UpdateServiceImpl self;


    private final Lock lock = new ReentrantLock();


    @Transactional
    public void updateDataTransactional() {
        // 模擬數據庫更新操作
        System.out.println("Updating data in transaction...");
        // 假設這里有一些耗時的數據庫操作
        Thread.sleep(1000);
    }


    public void updateData() {
        lock.lock();
        try {
            self.updateDataTransactional();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
    }
}

這種方式將事務管理和鎖操作分離到不同的方法中,既保證了事務的正確性,又避免了鎖失效的問題。

3. 使用數據庫鎖代替線程鎖

在某些情況下,我們可以考慮使用數據庫本身的鎖機制來替代線程鎖。數據庫鎖可以更加精確地控制對共享資源的訪問,且與事務管理緊密結合,不易出現鎖失效的問題。

五、總結

在@Transactional注解的方法內部使用線程鎖時,由于事務管理和鎖操作在時間上的不一致,可能會導致鎖失效的問題。為了解決這個問題,我們可以將事務管理和鎖操作分離,使用編程式事務管理,或者將需要事務支持的方法單獨提出來,并通過其他方式管理鎖。同時,我們也可以考慮使用數據庫鎖來替代線程鎖,以更好地保證數據的一致性和完整性。

希望這篇文章能幫助你更好地理解@Transactional中使用線程鎖導致的問題,并提供實用的解決方案。在實際開發中,根據具體場景選擇合適的方法,可以有效避免類似問題的發生。

責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2025-04-11 01:00:00

線程鎖Spring事務

2018-06-05 08:45:50

系統蘋果軟件

2017-08-11 15:56:03

內存價格暴漲

2013-11-29 10:02:41

移動廣告鎖屏分發

2020-12-18 08:28:13

Redis數據數據庫

2020-08-26 08:59:58

Linux線程互斥鎖

2022-09-14 19:50:22

事務場景流程

2020-03-27 16:27:03

Redis數據庫

2023-09-08 08:52:12

Spring注解事務

2023-08-29 10:51:44

2021-01-15 09:40:37

程序員技能開發者

2020-02-21 20:21:45

線程共享資源

2017-05-08 11:46:15

Java多線程

2019-04-12 15:14:44

Python線程

2019-01-04 11:18:35

獨享鎖共享鎖非公平鎖

2017-05-31 14:03:07

Java多線程內置鎖與顯示鎖

2023-09-28 09:07:54

注解失效場景

2022-06-15 07:32:35

Lock線程Java

2022-07-12 08:56:18

公平鎖非公平鎖Java

2019-11-28 16:00:06

重入鎖讀寫鎖樂觀鎖
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲欧美在线一区 | 一二三区在线 | 黄视频免费 | 国产精品成人一区二区三区 | 国产激情视频网站 | 国产网站在线免费观看 | 欧美激情综合网 | 亚洲国产精品久久 | 久久久.com | 国产综合精品一区二区三区 | 亚洲欧美日韩电影 | 国产精品69av | 日韩视频三区 | 一级黄色绿像片 | 亚洲aⅴ| 国产精品久久久爽爽爽麻豆色哟哟 | 欧美激情精品久久久久久 | wwwww在线观看 | 国产美女黄色 | 中文字幕专区 | 亚洲日韩中文字幕一区 | av日韩在线播放 | 懂色中文一区二区三区在线视频 | 成人国产免费观看 | 日韩欧美在线视频一区 | 婷婷综合激情 | 国产成人精品999在线观看 | 亚洲综合大片69999 | 久久成人国产 | 久草免费在线视频 | 99精品电影 | 91精品免费视频 | 精品国产18久久久久久二百 | 国产精品欧美一区二区三区 | 午夜大片 | 日韩伦理一区二区 | 久久精品视频播放 | 天天澡天天狠天天天做 | 亚洲免费人成在线视频观看 | 91精品国产91久久久久久最新 | 国产一区二区精品在线观看 |