Java中保證多線程間的數(shù)據(jù)共享的方法詳解
在討論這個(gè)問(wèn)題之前,我們可以先瞅瞅Java的內(nèi)存模型JMM,JMM可不要和JVM混為一談。我們說(shuō)的是內(nèi)存模型JMM(Java Memory Model)
。
Java的內(nèi)存模型
稍微解釋一下CPU的緩存,這里CPU的緩存有三級(jí),L1,L2和L3。
- L1是訪問(wèn)速度最快的,是線程獨(dú)享。
- L2次之,屬于內(nèi)核獨(dú)享。
- L3是最慢的,是多核共享。
當(dāng)CPU執(zhí)行指令需要數(shù)據(jù)的時(shí)候,會(huì)先在L1,L2,L3中依次尋找,若是找不到,則會(huì)去JVM中尋找,而JMM則在CPU和主內(nèi)存之間來(lái)保證我們需要的可見(jiàn)性和有序性。
這個(gè)JMM就是Java內(nèi)存模型的核心,可見(jiàn)性有序性都是在這里實(shí)現(xiàn)。
而主內(nèi)存就是JVM,就是我們的堆內(nèi)存。
保證可見(jiàn)性的方式
可見(jiàn)性是指,當(dāng)多個(gè)線程操作同一個(gè)數(shù)據(jù)的時(shí)候,保證一個(gè)線程的修改對(duì)其他線程是可見(jiàn)的,也就是說(shuō)不管多個(gè)線程如何操作,如何并發(fā),他們?cè)谕粫r(shí)間取到的值是相同的。
為了保證可見(jiàn)性,我們一般有以下幾種方案:
volatile
可以用volatile來(lái)修飾基本數(shù)據(jù)類型,可以保證每次CPU操作數(shù)據(jù)的時(shí)候,都直接操作的是主內(nèi)存的值。
文末我們會(huì)說(shuō)說(shuō)volatile的原理
synchronized
對(duì)于synchronized來(lái)說(shuō),是誰(shuí)拿到鎖,誰(shuí)執(zhí)行操作,對(duì)于拿到鎖的線程來(lái)說(shuō),前邊線程的操作是可見(jiàn)的。
我會(huì)另起一篇文章專門講解synchronized。
lock
lock是基于CAS
和volatile
的修改操作,可以保證操作數(shù)據(jù)時(shí)前邊操作的可見(jiàn)性。
final
final修飾的是常量
啊,沒(méi)法寫,只讀,當(dāng)然是全局可見(jiàn)的
這里有一個(gè)小點(diǎn):
我們清楚,volatile修飾基本數(shù)據(jù)類型的時(shí)候是可以保證可見(jiàn)性的,但是若是修飾的是引用數(shù)據(jù)類型呢?
一般沒(méi)人這么搞,甚至平時(shí)工作中volatile都很少使用,一不留神系統(tǒng)的性能會(huì)降低幾個(gè)維度。但是面試中常被問(wèn)到,我們可以這樣回答:若volatile修飾的是引用數(shù)據(jù)類型,則只能保證引用數(shù)據(jù)類型的地址是可見(jiàn)的,里邊的值不可見(jiàn)
。就這。
volatile的底層實(shí)現(xiàn)
稍微看看volatile的底層實(shí)現(xiàn)吧,其實(shí)
volatile的底層是匯編的lock指令,這個(gè)指令會(huì)
強(qiáng)行要求將值寫入主內(nèi)存
,并且忽略Store Buffer這種緩存,從而達(dá)到可見(jiàn)性的目的,同時(shí)利用MESI協(xié)議,讓其他緩存行失效。
我們曉得將java文件編譯為class文件的時(shí)候,會(huì)基于JIT做優(yōu)化,調(diào)整指令的順序,從而提升執(zhí)行效率,這個(gè)過(guò)程叫指令重排。
在CPU層面也會(huì)調(diào)整指令的順序來(lái)提升性能。而這個(gè)指令重排會(huì)導(dǎo)致一些問(wèn)題,我們看看volatile是如何解決這個(gè)問(wèn)題的。
原理
被volatile修飾的屬性,在編譯時(shí)會(huì)在先后增加內(nèi)存屏障。這里的提到的內(nèi)存屏障一般有四種
- SS: StoreStore屏障前的讀寫操作必須全部完成,才會(huì)繼續(xù)屏障之后的操作
- SL: StoreLoad屏障前的寫操作必須全部完成,才會(huì)繼續(xù)屏障之后的讀操作
- LL: LoadLoad屏障前的讀操作必須全部完成,才能繼續(xù)屏障之后的讀操作
- LS: LoadStore屏障前的讀操作必須全部完成,才能繼續(xù)屏障之后的寫操作
這里volatile的原理就如下
可以看到對(duì)于volatile的寫操作
之前添加了StoreStore
內(nèi)存屏障,必須完成之前的讀寫操作才能繼續(xù)volatile的寫操作。
而后添加了StoreLoad
屏障,要求其前邊的volatile寫操作完成,才能繼續(xù)之后的讀操作。
這樣就保證了在對(duì)volatile修飾的值執(zhí)行寫操作的時(shí)候,之前的讀寫操作已經(jīng)全部完成,而其后的讀操作在等待寫操作完成再去讀值。
而對(duì)于volatile的讀操作
在其后添加了一個(gè)LoadLoad屏障
和LoadStore屏障
,這里這個(gè)LoadLoad屏障不是很理解,查閱了諸多資料也沒(méi)有眉目,若是有小伙伴知曉的,煩請(qǐng)不吝賜教。
而LoadStore屏障
則保證了volatile的讀操作全部完成之后,再繼續(xù)之后的寫操作。
這樣volatile的讀操作完成之后,才會(huì)執(zhí)行其他的寫操作。保證了讀到的值是確定的不變的。
以上就是Java中保證多線程間的數(shù)據(jù)共享的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Java多線程間的數(shù)據(jù)共享的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot項(xiàng)目編寫發(fā)送異常日志到企微工具包的操作方法
本文介紹了Springboot項(xiàng)目如何編寫發(fā)送異常日志到企業(yè)微信的工具包,內(nèi)容包括創(chuàng)建基礎(chǔ)Bean、配置類、pom依賴等步驟,并展示了如何通過(guò)nacos進(jìn)行配置,這為開(kāi)發(fā)者提供了一種有效的日志管理方案,方便快速定位和處理項(xiàng)目中的異常問(wèn)題,感興趣的朋友跟隨小編一起看看吧2024-09-09SpringBoot配置及使用Schedule過(guò)程解析
這篇文章主要介紹了SpringBoot配置及使用Schedule過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04nacos一直頻繁的打印日志get changegroupkeys問(wèn)題
這篇文章主要介紹了nacos一直頻繁的打印日志get changegroupkeys問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05基于JPA實(shí)體類監(jiān)聽(tīng)器@EntityListeners注解的使用實(shí)例
這篇文章主要介紹了JPA實(shí)體類監(jiān)聽(tīng)器@EntityListeners注解的使用實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08maven關(guān)于pom文件中的relativePath標(biāo)簽使用
在Maven項(xiàng)目中,子工程通過(guò)<relativePath>標(biāo)簽指定父工程的pom.xml位置,以確保正確繼承父工程的配置,這個(gè)標(biāo)簽可以配置為默認(rèn)值、空值或自定義值,默認(rèn)情況下,Maven會(huì)向上一級(jí)目錄尋找父pom;若配置為空值2024-09-09Java編程實(shí)現(xiàn)beta分布的采樣或抽樣實(shí)例代碼
這篇文章主要介紹了Java編程實(shí)現(xiàn)beta分布的采樣或抽樣實(shí)例,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01SpringBoot MyBatis簡(jiǎn)單快速入門例子
MyBatis 是一款優(yōu)秀的持久層框架,它支持自定義 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射。這篇文章主要介紹了SpringBoot MyBatis快速入門-簡(jiǎn)單例子,需要的朋友可以參考下2021-07-07SpringBoot+MybatisPlus+Mysql+Sharding-JDBC分庫(kù)分表
本文主要介紹了SpringBoot+MybatisPlus+Mysql+Sharding-JDBC分庫(kù)分表,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03