javascript
在Spring事务管理下,Synchronized为啥还线程不安全?
在synchronized 鎖住方法的情況下,竟然出現(xiàn)了臟寫
Tips
昨天本來打算是準備著一支煙 一杯咖啡 一個bug寫一天的,突然我們組長跟我們說線上環(huán)境報錯了,
還出現(xiàn)了"服務器異常,請聯(lián)系管理員"
這特么不是一級事故嗎?雖然有測試再前面扛槍。但是是我負責的直播模塊,心理慌的一批(ps 報錯圖當時沒保存了)
分析事故原因
因為是報錯(因為我做這條數(shù)據(jù)查詢的時候是selectOne 所以會報出現(xiàn)了sql異常) 原因到是很快找到了 數(shù)據(jù)庫出現(xiàn)了臟寫如圖:
?
我負責的是直播模塊 其中的一個業(yè)務是直播結(jié)束后第三方會通知我去拉取直播的回放,
但是這個回放有可能一條,也有可能是多條,但是我們的業(yè)務要求是只需要保存一條直播回放所以我這會做如下操作:
我再做插入之前我會做一個校驗,并且我還加了一個方法級別的鎖 并且線上我們只有一個副本,
竟然還出現(xiàn)了臟寫 我的fuck,我這是見了鬼了吧
解決問題的過程
我懷著百私不得其解的心理打算去找答案
首先我模擬了一個并發(fā)環(huán)境:
@Testpublic void TEST_TX() throws Exception {int N = 2;CountDownLatch latch = new CountDownLatch(N);for (int i = 0; i < N; i++) {Thread.sleep(100L);new Thread(() -> {try {latch.await();System.out.println("---> start " + Thread.currentThread().getName());Thread.sleep(1000L);CourseChapterLiveRecord courseChapterLiveRecord = new CourseChapterLiveRecord();courseChapterLiveRecord.setCourseChapterId(9785454l);courseChapterLiveRecord.setCreateTime(new Date());courseChapterLiveRecord.setRecordEndTime(new Date());courseChapterLiveRecord.setDuration("aaa");courseChapterLiveRecord.setSiteDomain("ada");courseChapterLiveRecord.setRecordId("aaaaaaaaa");courseChapterLiveRecordServiceImpl.saveCourseChapterLiveRecord(courseChapterLiveRecord);System.out.println("---> end " + Thread.currentThread().getName());} catch (Exception e) {e.printStackTrace();}}).start();latch.countDown();}}通過CountDownLatch 去模擬并發(fā)看看數(shù)據(jù)是否會有問題:結(jié)果測試線的數(shù)據(jù)如下:
我去還真出現(xiàn)了 而且是一部分出現(xiàn)臟寫,一部分沒有成功,我特么?fuck?心理一萬次想說這特么我怎么找
測了十來次 然后覺得肯定是有問題的 然后冷靜下來 因為我打了日志 發(fā)現(xiàn)2個線程確實是順序執(zhí)行的(這里的截圖就沒有貼了)
眾所周知,synchronized方法能夠保證所修飾的代碼塊、方法保證有序性、原子性、可見性。 那么這說明什么呢 我一想肯定Synchronized?它是起到它的作用的 一個線程執(zhí)行完成之后,另外一個線程再來執(zhí)行, 突然靈光一閃 是不是下一個線程再做冪等校驗的時候 讀到了上一次還沒有提交的事務 所以造成了臟讀,臟寫的原因呢 然后我把再類上的 @Transactional 注解去掉
果然后面測了幾次 再也沒出現(xiàn)上面的情況了
Tips?特別感謝一位不愿透露姓名的大佬的指出說我沒有把標題的內(nèi)容說清楚和后面的解決問題的收場的時候有點草率
在這里 我再好好的說一下我標題是 在Spring事務管理下,Synchronized為啥還線程不安全? 其實有是自己并沒有用Synchronized 鎖住 Spring 的事務
因為我的列子上的@Transaction注解是再類上面(也就是再方法上面)Spring的聲明事事務他是利用了aop的思想
我雖然鎖住了第一個線程 但是等到第一個線程的事務 還沒提交的時候,第二個線程就去查詢了 所以就會導致線程不安全問題
解決問題
方案1 很簡單 那就是不開事務就行了,再這個方法上不加事務就行 因為 Synchronized 可以保證線程安全。 這個方案的意思就是說不要再同一個方法上用@Transaction 和 Synchronized 例子圖就沒有貼了 就像我前面的 把注解去掉就好了 (但是前提你這個方案確定是不需要事務)
方案2 再這個里面再調(diào)用一層service 讓那個方法提交事務,這樣的話加上Synchronized 也能保證線程安全。 方案2我貼下代碼吧
@Overridepublic synchronized void saveCourseChapterLiveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {saveRecord(courseChapterLiveRecord);}@Transactionalpublic void saveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {//先查數(shù)據(jù)看是否已經(jīng)存了if (findOrder(courseChapterLiveRecord)){ return;}int row = this.insertSelective(courseChapterLiveRecord);if (row<1){log.info("把錄播的信息插入數(shù)據(jù)庫失敗 參數(shù)是->{}", JSON.toJSONString(courseChapterLiveRecord));throw new RRException("把錄播的信息插入數(shù)據(jù)庫失敗");} }其實也就是說把事務包裹在Synchronized 里面
先自我批評一下
在技術的道路上真的不要自己覺得是什么就是什么 上面的代碼是錯誤的 其實我并沒有測試過 就貼到文章上了 這是一個大忌 為什么很多技術文章有問題 因為很多就像我上面的一樣 所以敦促自己以后做事情還是要扎扎實實
感謝?紫雨飛星?讀者提出我的錯誤 具體錯誤的原因是因為調(diào)用savRecord方法的時候使用的是this對象,其實是沒有被AOP處理的,也就是這個Transactional不會生效~~~
修改后的代碼 自己注入自己
@Overridepublic synchronized void saveCourseChapterLiveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {courseChapterLiveRecordServiceImpl.saveRecord(courseChapterLiveRecord);}@Transactionalpublic void saveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {//先查數(shù)據(jù)看是否已經(jīng)存了if (findOrder(courseChapterLiveRecord)){ return;}int row = this.insertSelective(courseChapterLiveRecord);if (row<1){log.info("把錄播的信息插入數(shù)據(jù)庫失敗 參數(shù)是->{}", JSON.toJSONString(courseChapterLiveRecord));throw new RRException("把錄播的信息插入數(shù)據(jù)庫失敗");} }利用中午的時間測了幾次 確實是不會出現(xiàn)線程安全問題了
方案3 用redis 分布式鎖 也是可以的 就算是多個副本也是能保證線程安全。這個后面的文章會有寫到
結(jié)論
在多線程環(huán)境下,就可能會出現(xiàn):方法執(zhí)行完了(synchronized代碼塊執(zhí)行完了),事務還沒提交,別的線程可以進入被synchronized修飾的方法,再讀取的時候,讀到的是還沒提交事務的數(shù)據(jù),這個數(shù)據(jù)不是最新的,所以就出現(xiàn)了這個問題。
Synchronized 失效關鍵原因:是因為Synchronized鎖定的是當前調(diào)用方法對象,而Spring AOP 處理事務會進行生成一個代理對象,并在代理對象執(zhí)行方法前的事務開啟,方法執(zhí)行完的事務提交,所以說,事務的開啟和提交并不是在 Synchronized 鎖定的范圍內(nèi)。出現(xiàn)同步鎖失效的原因是:當A(線程) 執(zhí)行完insertSelective()方法,會進行釋放同步鎖,去做提交事務,但在A(線程)還沒有提交完事務之前,B(線程)進行執(zhí)行findOrder() 方法,執(zhí)行完畢之后和A(線程)一起提交事務, 這時候就會出現(xiàn)線程安全問題。
總結(jié)
以上是生活随笔為你收集整理的在Spring事务管理下,Synchronized为啥还线程不安全?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot 发邮件和附件,超
- 下一篇: 艰难的这年,程序员的未来在哪里?