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