Java内存模型与happens-before原则
Java內(nèi)存模型
Java內(nèi)存模型不同于Jvm內(nèi)存模型,Java內(nèi)存模型(JMM)規(guī)定了JVM必須遵循一組最小保證,這組保證規(guī)定了對(duì)變量的寫入操作在何時(shí)將于其他線程可見(jiàn)。
在Java虛擬機(jī)規(guī)范中試圖定義一種Java內(nèi)存模型(Java Memory Model,JMM),JVM通過(guò)在適當(dāng)?shù)奈恢貌迦雰?nèi)存柵欄來(lái)屏蔽JMM和各個(gè)硬件平臺(tái)和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果。Java內(nèi)存模型并沒(méi)有限制執(zhí)行引擎使用處理器的寄存器或者高速緩存來(lái)提升指令執(zhí)行速度,也沒(méi)有限制編譯器對(duì)指令進(jìn)行重排序。也就是說(shuō),在java內(nèi)存模型中,也會(huì)存在緩存一致性問(wèn)題和指令重排序的問(wèn)題。
Java內(nèi)存模型規(guī)定所有的變量都是存在主存當(dāng)中(類似于前面說(shuō)的物理內(nèi)存),每個(gè)線程都有自己的工作內(nèi)存(類似于前面的高速緩存)。線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接對(duì)主存進(jìn)行操作。并且每個(gè)線程不能訪問(wèn)其他線程的工作內(nèi)存。
如圖:
一,重排序
再?zèng)]有充分同步的程序中,如果調(diào)度器采用不恰當(dāng)?shù)姆绞絹?lái)交替執(zhí)行不同線程的操作,那么將導(dǎo)致不正確的結(jié)果。更糟糕的是,JMM還使得不同線程看到的操作執(zhí)行順序是不同的,從而導(dǎo)致在缺乏同步的情況下,要推斷操作的執(zhí)行順序更加復(fù)雜,各種使得操作延遲或者砍死亂序執(zhí)行的不同原因,都可以稱為“重排序”
將緩存刷新到主內(nèi)存的不同時(shí)序也可能會(huì)導(dǎo)致重排序。
重排序的基礎(chǔ)是前后語(yǔ)句不存在依賴關(guān)系時(shí),才有可能發(fā)生指令重排序。
內(nèi)存柵欄會(huì)屏蔽重排序
兩個(gè)操作之間存在happens-before關(guān)系,并不意味著Java平臺(tái)的具體實(shí)現(xiàn)必須要按照happens-before關(guān)系指定的順序來(lái)執(zhí)行。如果重排序之后的執(zhí)行結(jié)果,與按happens-before關(guān)系來(lái)執(zhí)行的結(jié)果一致,那么這種重排序并不非法(也就是說(shuō),JMM允許這種重排序)
二,happens-before
happens-before原則是指如果A和B滿足這個(gè)happens-before原則,則可以保證操作B的線程可以看到操作A的結(jié)果。
當(dāng)一個(gè)變量被多個(gè)線程讀取并且至少被一個(gè)線程寫入時(shí),如果在讀操作和寫操作之間沒(méi)有按照H-B原則來(lái)排序,就會(huì)產(chǎn)生數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題,在正確同步的程序中不存在數(shù)據(jù)競(jìng)爭(zhēng),并且會(huì)表現(xiàn)出串行一致性。
1,程序順序原則
單線程中,寫在前面的操作A會(huì)在寫在后面的操作B之前執(zhí)行。
2,鎖原則
在鎖上的解鎖操作必須在鎖上的加鎖操作之前執(zhí)行。
3,volatile變量原則
對(duì)volatile變量的寫入操作必須在對(duì)該變量的讀操作之前執(zhí)行。
4,線程啟動(dòng)原則
在一個(gè)線程上,start操作必須在該線程執(zhí)行任何操作之前執(zhí)行。
5,線程關(guān)閉原則
線程中任何操作都必須在其他線程檢測(cè)到該線程已經(jīng)結(jié)束之前執(zhí)行。
6,線程中斷原則
當(dāng)一個(gè)線程在另一個(gè)線程調(diào)用interrupt時(shí),必須在被中斷線程檢測(cè)到interrupt調(diào)用之前執(zhí)行。‘’
7,終結(jié)器規(guī)則。
對(duì)象的構(gòu)造函數(shù)必須在啟動(dòng)該對(duì)象的終結(jié)器之前執(zhí)行完成。
8,傳遞性
如果操作A H-B 操作B,操作B H-B 操作C 則 操作A H-B 操作C
如果2個(gè)在不同線程的操作不滿足H-B原則,則無(wú)法推斷一個(gè)操作是否一定在另一個(gè)操作之前。
如:
這里,在多個(gè)線程來(lái)調(diào)用時(shí),因?yàn)椴粷M足H-B,所以第一個(gè)線程調(diào)用時(shí),已經(jīng)初始化了resource,沒(méi)法保證第二個(gè)線程來(lái)時(shí),獲取到的resource到底是拿到的null還是一個(gè)失效值。
三,借助同步
由于H-B排序功能很強(qiáng)大,因此有時(shí)候可以“借助”現(xiàn)有同步機(jī)制的可見(jiàn)性屬性。
“借助同步”技術(shù)是指借助于現(xiàn)有的H-B原則,來(lái)確保對(duì)象X的可見(jiàn)性,而不是為了發(fā)布X而創(chuàng)建一個(gè)H-B順序。
在類庫(kù)中提供的其他H-B順序:
四,并發(fā)編程的三個(gè)問(wèn)題
1,原子性
一個(gè)操作要么全部執(zhí)行,要么不執(zhí)行。
JAVA中原子性靠synchronized和Lock來(lái)實(shí)現(xiàn),或者native方法的cas等
2,可見(jiàn)性
多個(gè)線程訪問(wèn)一個(gè)變量時(shí),一個(gè)線程修改后其他線程可以立即看到這個(gè)值的改變。
Java提供了volatile關(guān)鍵字來(lái)保證可見(jiàn)性
3,有序性
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。
Java中保證了單線程下看起來(lái)有序(as if serial),不保證多線程下有序。
因?yàn)樵贘ava內(nèi)存模型中,允許編譯器和處理器對(duì)指令進(jìn)行重排序,但是重排序過(guò)程不會(huì)影響到單線程程序的執(zhí)行,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性。
在Java里面,可以通過(guò)volatile關(guān)鍵字來(lái)保證一定的“有序性”(具體原理在下一節(jié)講述)。另外可以通過(guò)synchronized和Lock來(lái)保證有序性,很顯然,synchronized和Lock保證每個(gè)時(shí)刻是有一個(gè)線程執(zhí)行同步代碼,相當(dāng)于是讓線程順序執(zhí)行同步代碼,自然就保證了有序性。
另外,Java內(nèi)存模型具備一些先天的“有序性”,即不需要通過(guò)任何手段就能夠得到保證的有序性,這個(gè)通常也稱為 happens-before 原則。如果兩個(gè)操作的執(zhí)行次序無(wú)法從happens-before原則推導(dǎo)出來(lái),那么它們就不能保證它們的有序性,虛擬機(jī)可以隨意地對(duì)它們進(jìn)行重排序。
總結(jié)
以上是生活随笔為你收集整理的Java内存模型与happens-before原则的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 线程池工作原理
- 下一篇: 深入浅出理解锁之—— AbstractQ