日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

在Spring事务管理下,Synchronized为啥还线程不安全?

發(fā)布時間:2025/3/21 javascript 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 在Spring事务管理下,Synchronized为啥还线程不安全? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在synchronized 鎖住方法的情況下,竟然出現(xiàn)了臟寫

Tips
昨天本來打算是準(zhǔn)備著一支煙 一杯咖啡 一個bug寫一天的,突然我們組長跟我們說線上環(huán)境報錯了,
還出現(xiàn)了"服務(wù)器異常,請聯(lián)系管理員"
這特么不是一級事故嗎?雖然有測試再前面扛槍。但是是我負(fù)責(zé)的直播模塊,心理慌的一批(ps 報錯圖當(dāng)時沒保存了)

分析事故原因

因為是報錯(因為我做這條數(shù)據(jù)查詢的時候是selectOne 所以會報出現(xiàn)了sql異常) 原因到是很快找到了 數(shù)據(jù)庫出現(xiàn)了臟寫如圖:

?

我負(fù)責(zé)的是直播模塊 其中的一個業(yè)務(wù)是直播結(jié)束后第三方會通知我去拉取直播的回放,
但是這個回放有可能一條,也有可能是多條,但是我們的業(yè)務(wù)要求是只需要保存一條直播回放所以我這會做如下操作:

我再做插入之前我會做一個校驗,并且我還加了一個方法級別的鎖 并且線上我們只有一個副本,
竟然還出現(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í)行, 突然靈光一閃 是不是下一個線程再做冪等校驗的時候 讀到了上一次還沒有提交的事務(wù) 所以造成了臟讀臟寫的原因呢 然后我把再類上的 @Transactional 注解去掉

果然后面測了幾次 再也沒出現(xiàn)上面的情況了

Tips?特別感謝一位不愿透露姓名的大佬的指出說我沒有把標(biāo)題的內(nèi)容說清楚和后面的解決問題的收場的時候有點草率

在這里 我再好好的說一下我標(biāo)題是 在Spring事務(wù)管理下,Synchronized為啥還線程不安全? 其實有是自己并沒有用Synchronized 鎖住 Spring 的事務(wù)
因為我的列子上的@Transaction注解是再類上面(也就是再方法上面)Spring的聲明事事務(wù)他是利用了aop的思想
我雖然鎖住了第一個線程 但是等到第一個線程的事務(wù) 還沒提交的時候,第二個線程就去查詢了 所以就會導(dǎo)致線程不安全問題

解決問題

方案1 很簡單 那就是不開事務(wù)就行了,再這個方法上不加事務(wù)就行 因為 Synchronized 可以保證線程安全。 這個方案的意思就是說不要再同一個方法上用@Transaction 和 Synchronized 例子圖就沒有貼了 就像我前面的 把注解去掉就好了 (但是前提你這個方案確定是不需要事務(wù))

方案2 再這個里面再調(diào)用一層service 讓那個方法提交事務(wù),這樣的話加上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ù)庫失敗");} }

其實也就是說把事務(wù)包裹在Synchronized 里面

先自我批評一下
在技術(shù)的道路上真的不要自己覺得是什么就是什么 上面的代碼是錯誤的 其實我并沒有測試過 就貼到文章上了 這是一個大忌 為什么很多技術(shù)文章有問題 因為很多就像我上面的一樣 所以敦促自己以后做事情還是要扎扎實實

感謝?紫雨飛星?讀者提出我的錯誤 具體錯誤的原因是因為調(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í)行完了),事務(wù)還沒提交,別的線程可以進入被synchronized修飾的方法,再讀取的時候,讀到的是還沒提交事務(wù)的數(shù)據(jù),這個數(shù)據(jù)不是最新的,所以就出現(xiàn)了這個問題。

Synchronized 失效關(guān)鍵原因:是因為Synchronized鎖定的是當(dāng)前調(diào)用方法對象,而Spring AOP 處理事務(wù)會進行生成一個代理對象,并在代理對象執(zhí)行方法前的事務(wù)開啟,方法執(zhí)行完的事務(wù)提交,所以說,事務(wù)的開啟和提交并不是在 Synchronized 鎖定的范圍內(nèi)。出現(xiàn)同步鎖失效的原因是:當(dāng)A(線程) 執(zhí)行完insertSelective()方法,會進行釋放同步鎖,去做提交事務(wù),但在A(線程)還沒有提交完事務(wù)之前,B(線程)進行執(zhí)行findOrder() 方法,執(zhí)行完畢之后和A(線程)一起提交事務(wù), 這時候就會出現(xiàn)線程安全問題。

總結(jié)

以上是生活随笔為你收集整理的在Spring事务管理下,Synchronized为啥还线程不安全?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。