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