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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

3.线程安全之可见性、有序性、原子性是什么?

發(fā)布時間:2023/12/20 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 3.线程安全之可见性、有序性、原子性是什么? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

小陳:上一篇說了JAVA內(nèi)存模型,但是后面說了在多線程并發(fā)操作的時候有可見性問題,我現(xiàn)在迫不及待想知道線程安全的可見性、原子性、有序性是啥了

老王:哈哈,可以。我先說說我自己對可見性、有序性、原子性的理解:

可見性

上一篇講了,多個線程同時對某一個共享變量進(jìn)行操作的時候,存在線程A的操作對線程B不可見的問題。簡單來說就是線程A執(zhí)行了某些操作對數(shù)據(jù)進(jìn)行了變更;但是線程B并不知道,所以還是使用舊數(shù)據(jù)干它自己的活。

小陳:這么講,按照概念來理解我還是很模糊啊,能不能搞個例子來講解一下?

老王,沒問題,我就結(jié)合上次你提的那個JAVA內(nèi)存模型可能導(dǎo)致數(shù)據(jù)不一致的這個例子給你講解一下

比如線程A線程B都執(zhí)行x++操作(x的初始值是0),線程A執(zhí)行完了之后將主內(nèi)存的值更新為1,但是程B由于已經(jīng)將 x = 0?讀取進(jìn)入自己的工作內(nèi)存了不知道線程A將x更新為1了,所以還是使用x=0去進(jìn)行++操作。

像這種,就是典型的可見性問題,就是線程A操作了數(shù)據(jù),但是線程B不可見,感知不到

小陳:嘿嘿,看來上次我的猜測沒錯啊,無論是CPU緩存架構(gòu)下還是JAVA內(nèi)存模型都是有可見性的問題。

老王:沒錯,你說的這個問題是存在的,但是還是有些手段可以避免的,后面我們再來討論。下面我們再來說一下有序性的問題

有序性

有序性是指由于JIT動態(tài)編譯器、操作系統(tǒng)為了給提高程序的執(zhí)行效率,可能會對按順序書寫好的指令進(jìn)行重排線程或者CPU執(zhí)行的時候不一定按照程序書寫的順序來執(zhí)行

比如程序的書寫順序是 指令1 -> 指令2 -> 指令3;但是由于指令重排序,某個線程執(zhí)行這幾個指令的時候,比如說線程A執(zhí)行的時候,可能先執(zhí)行指令3,然后再執(zhí)行指令2、指令1。導(dǎo)致別的線程,比如說線程B看到線程A的指令執(zhí)行是亂序的

我搞個代碼給你講解一下:

線程A執(zhí)行數(shù)據(jù)庫、http客戶端初始化工作,初始化完畢之后將initOk初始化表示置為true表示初始化完畢。

// 步驟1 dataSource = initDataSource(); // 步驟2 httpClient = initHttpClient(); // 步驟3 initOK = true;

線程B在這里一直監(jiān)聽線程A是否初始化資源完畢,看到initOK標(biāo)識為true表示初始化結(jié)束。開始執(zhí)行業(yè)務(wù)操作,獲取數(shù)據(jù),根據(jù)數(shù)據(jù)發(fā)起網(wǎng)絡(luò)調(diào)用

// 步驟4 while(!initOK) { } // 步驟5 Object data = dataSource.getData(); // 步驟6 httpClient.request(data);

上面這段代碼,正常來說線程A執(zhí)行順序應(yīng)該是 步驟1 -> 步驟2 -> 步驟3。但是由于JIT動態(tài)編譯器或者操作系統(tǒng)可能對指令進(jìn)行重排序,所以可能執(zhí)行順序是 步驟3 -> 步驟1 -> 步驟2

這樣就會導(dǎo)致線程B先看到了initOk = true,這樣就會導(dǎo)致線程B直接跳出while循環(huán),跳出等待,執(zhí)行dataSource.getData方法,執(zhí)行httpClient.request()方法;但是線程A的步驟1、步驟2還沒執(zhí)行dataSource、httpClient是null,會拋出空指針異常

小陳:等等,我來理解一下;線程A先執(zhí)行了initOK = true導(dǎo)致線程B跳出了while循環(huán),然后調(diào)用dataSource.getData方法,由于線程A還沒執(zhí)行dataSource = initDataSource()方法,所以dataSource對象可能是null值,這樣線程B調(diào)用的時候可能拋出空指針異常,是這樣吧?

老王:沒錯,理解得非常好,小陳你果然聰明啊;你這個理解力,我對后面講解的文章越來越有信心了。

小陳:嘿嘿......

老王:上面這種有序性問題,在多線程并發(fā)執(zhí)行的時候,由于指令的重排序存在,很可能是會發(fā)生的。

這就是有序性帶來的線程安全問題,也就是線程B看到線程A的執(zhí)行時亂序的,也就是不是按照步驟1、2、3這樣順序的來執(zhí)行。

簡單點(diǎn)來講就是線程A還沒初始化好,就將標(biāo)識initOk設(shè)置為true。導(dǎo)致線程B誤以為線程A搞定了,然后去獲取數(shù)據(jù),發(fā)起http請求,然后...,然后線程B就掛了...(線程B:線程A這坑爹的,還沒初始化好就告訴我搞定了,這不是坑我嘛...)

老王:說了可見性、有序性的問題,下面我們再來說說原子性問題。

原子性

老王:原子性是說某個操作是不可分割的、不可中斷的。

小陳:這個不可分割、不可中斷是啥意思?

老王:

比如之前說的JAVA內(nèi)存模型定義的8中操作;read、load、use、assign、store、write、lock、unlock等八種指令都是原子的。

老王:比如說read指令,不可分割:說的是這條指令是讀取數(shù)據(jù)最小的指令了,不能再拆分成更多的指令

小陳:不可分割是不是說它就是最小的執(zhí)行單元了,不能被拆分的意思?

老王:沒錯,就是這個意思.....

小陳:哦哦,這個不可分割我懂了,那不可中斷又是啥玩意?

老王:簡單來講就是不能執(zhí)行到一半就不干了,比如這個read指令,你不能讀取一個變量的數(shù)據(jù),只讀取到一半的時候就撂挑子不干了;要執(zhí)行就一起全部執(zhí)行,不能干了一半就不干了,同時也不能被其它外部的因素打斷了。

小陳:那意思是說cpu執(zhí)行read指令,執(zhí)行到一半的時候,就把這個線程掛起來,這個是不被允許的咯。

老王:哈哈,就是這樣的。要干就全部都干了,不能中途搞了一半你跟我說退出了....

小陳:老王你真牛逼,這么晦澀的東西都被你三言兩句簡單的話就給說清楚了。

老王:那是,畢竟我可是單身十多年...;不,是工作十多年的老兵了,“技巧”早就磨練的杠杠的......

小陳:......

小陳:道理我是聽明白了,實(shí)際編碼里面那些操作是原子的,那些不是原子的呢?

老王:給你講講下面的例子就知道了:

比如下面的操作:

(1)y = 1;

(2)x++;

(3)z = y;

(1)其中y = 1操作是原子的,因?yàn)橹皇菆?zhí)行了load操作,將1直接loady,只有一條指令的執(zhí)行。??

(2) x++操作就不是原子性的,之前畫圖講解過,i++操作經(jīng)過,read、load、use、assign、store、write六個操作;雖然每個指令都是原子的,但是合并起來并不是原子的

比如說線程A執(zhí)行readload操作將工作內(nèi)存的變量x的值載入自己工作內(nèi)存的變量副本中。但是還沒來得及執(zhí)行后續(xù)的use、assign、store、write指令,這個時候線程A就被掛起了

線程A被掛起期間線程B就也執(zhí)行了read、load指令變量x放入線程B的工作內(nèi)存里了。這就相當(dāng)于線程A的這6條指令沒有連續(xù)執(zhí)行完,被中斷了,中途CPU又去執(zhí)行別的指令了,并不是不可分割、不可中斷的。

(3)z = y 也不是原子的,它先要執(zhí)行read指令讀取y的值,然后執(zhí)行l(wèi)oad執(zhí)行賦值給z。并不是單一的原子指令

小陳:哇塞,老王你太牛逼了,你這么說我全懂了。

小陳:既然多線程并發(fā)操作的時候會有這些問題,那操作系統(tǒng)或者說JAVA底層是怎么解決這些問題達(dá)到并發(fā)安全的效果的呢?

老王:操作系統(tǒng)設(shè)計者肯定是會想到這些問題的,這就是我們下面要慢慢講解的話題了,操作系統(tǒng)或者JAVA底層是怎么解決這些并發(fā)安全的問題的。

老王:小陳,給你個任務(wù),你去看看MESI一致性協(xié)議的內(nèi)容,下面我們講解一下MESI一致性協(xié)議,以及MESI一致性協(xié)議是如何解決可見性問題的。

關(guān)注小陳,公眾號上更多更全的文章

JAVA并發(fā)文章目錄(公眾號)

JAVA并發(fā)專題 《筑基篇》

1.什么是CPU多級緩存模型?

2.什么是JAVA內(nèi)存模型?

3.線程安全之可見性、有序性、原子性是什么?

4.什么是MESI緩存一致性協(xié)議?怎么解決并發(fā)的可見性問題?

JAVA并發(fā)專題《練氣篇》

5.volatile怎么保證可見性?

6.什么是內(nèi)存屏障?具有什么作用?

7.volatile怎么通過內(nèi)存屏障保證可見性和有序性?

8.volatile為啥不能保證原子性?

9.synchronized是個啥東西?應(yīng)該怎么使用?

10.synchronized底層之monitor、對象頭、Mark Word?

11.synchronized底層是怎么通過monitor進(jìn)行加鎖的?

12.synchronized的鎖重入、鎖消除、鎖升級原理?無鎖、偏向鎖、輕量級鎖、自旋、重量級鎖

13.synchronized怎么保證可見性、有序性、原子性?

JAVA并發(fā)專題《結(jié)丹篇》

14. JDK底層Unsafe類是個啥東西?

15.unsafe類的CAS是怎么保證原子性的?

16.Atomic原子類體系講解

17.AtomicInteger、AtomicBoolean的底層原理

18.AtomicReference、AtomicStampReference底層原理

19.Atomic中的LongAdder底層原理之分段鎖機(jī)制

20.Atmoic系列Strimped64分段鎖底層實(shí)現(xiàn)源碼剖析

JAVA并發(fā)專題《金丹篇》

21.AQS是個啥?為啥說它是JAVA并發(fā)工具基礎(chǔ)框架?

22.基于AQS的互斥鎖底層源碼深度剖析

23.基于AQS的共享鎖底層源碼深度剖析

24.ReentrantLock是怎么基于AQS實(shí)現(xiàn)獨(dú)占鎖的?

25.ReentrantLock的Condition機(jī)制底層源碼剖析

26.CountDownLatch 門栓底層源碼和實(shí)現(xiàn)機(jī)制深度剖析

27.CyclicBarrier 柵欄底層源碼和實(shí)現(xiàn)機(jī)制深度剖析

28.Semaphore 信號量底層源碼和實(shí)現(xiàn)機(jī)深度剖析

29.ReentrantReadWriteLock 讀寫鎖怎么表示?

30. ReentrantReadWriteLock 讀寫鎖底層源碼和機(jī)制深度剖析

JAVA并發(fā)專題《元神篇》并發(fā)數(shù)據(jù)結(jié)構(gòu)篇

31.CopyOnAarrayList 底層分析,怎么通過寫時復(fù)制副本,提升并發(fā)性能?

32.ConcurrentLinkedQueue 底層分析,CAS 無鎖化操作提升并發(fā)性能?

33.ConcurrentHashMap詳解,底層怎么通過分段鎖提升并發(fā)性能?

34.LinkedBlockedQueue 阻塞隊(duì)列怎么通過ReentrantLock和Condition實(shí)現(xiàn)?

35.ArrayBlockedQueued 阻塞隊(duì)列實(shí)現(xiàn)思路竟然和LinkedBlockedQueue一樣?

36.DelayQueue 底層源碼剖析,延時隊(duì)列怎么實(shí)現(xiàn)?

37.SynchronousQueue底層原理解析

JAVA并發(fā)專題《飛升篇》線程池底層深度剖析

38. 什么是線程池?看看JDK提供了哪些默認(rèn)的線程池?底層竟然都是基于ThreadPoolExecutor的?

39.ThreadPoolExecutor 構(gòu)造函數(shù)有哪些參數(shù)?這些參數(shù)分別表示什么意思?

40.內(nèi)部有哪些變量,怎么表示線程池狀態(tài)和線程數(shù),看看道格.李大神是怎么設(shè)計的?

41. ThreadPoolExecutor execute執(zhí)行流程?怎么進(jìn)行任務(wù)提交的?addWorker方法干了啥?什么是workder?

42. ThreadPoolExecutor execute執(zhí)行流程?何時將任務(wù)提交到阻塞隊(duì)列? 阻塞隊(duì)列滿會發(fā)生什么?

43. ThreadPoolExecutor 中的Worker是如何執(zhí)行提交到線程池的任務(wù)的?多余Worker怎么在超出空閑時間后被干掉的?

44. ThreadPoolExecutor shutdown、shutdownNow內(nèi)部核心流程

45. 再回頭看看為啥不推薦Executors提供幾種線程池?

46. ThreadPoolExecutor線程池篇總結(jié)

總結(jié)

以上是生活随笔為你收集整理的3.线程安全之可见性、有序性、原子性是什么?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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