濟(jì)南中建設(shè)計(jì)院網(wǎng)站權(quán)重查詢工具
目錄
一、Happens-Before模型簡(jiǎn)介
二、組成Happens-Before模型的八種規(guī)則
2.1 程序順序規(guī)則(as-if-serial語義)
2.2 傳遞性規(guī)則
2.3 volatile變量規(guī)則
2.4 監(jiān)視器鎖規(guī)則
2.5 start規(guī)則
2.6 Join規(guī)則
一、Happens-Before模型簡(jiǎn)介
除了顯示引用volatile關(guān)鍵字能夠保證可見性以外,在Java中,還有很多的可見性保障的規(guī)則。
從JDK1.5開始,引入了一個(gè)happens-before的概念來闡述多個(gè)線程操作共享變量的可見性問題。所以我們可以認(rèn)為在JMM中,如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見,那么這兩個(gè)操作必須要存在 happens-before關(guān)系。這兩個(gè)操作可以是同一個(gè)線程,也可以是不同的線程。
JMM可以通過happens-before關(guān)系向程序員提供跨線程的內(nèi)存可見性,即如果A線程的寫操作a與B線程的讀操作b之間存在happens-before關(guān)系,盡管a操作和b操作不在同一個(gè)線程中執(zhí)行,但是JMM向程序員保證a操作對(duì)b操作可見。具體定義為:
- 如果一個(gè)操作happens-before另外一個(gè)操作(發(fā)生在另一個(gè)操作之前),那么第一個(gè)操作的執(zhí)行結(jié)果對(duì)第二個(gè)操作可見,且第一個(gè)操作的執(zhí)行順序在第二個(gè)操作之前。
- 兩個(gè)操作之間存在happens-before關(guān)系,并不意味著Java平臺(tái)的具體實(shí)現(xiàn)必須要按照happens-before關(guān)系指定的順序來執(zhí)行。如果重排序之后的執(zhí)行結(jié)果,與按happens-before關(guān)系來執(zhí)行的結(jié)果一致,那么這種重排序并不非法(也就是說,JMM允許這種重排序)。
上面兩條規(guī)則,第一條,是JMM對(duì)程序員的保證。從程序員的角度可以這樣理解happens-before:如果A happens-before B ,那么java內(nèi)存模型將向程序員保證————A操作的結(jié)果一定對(duì)B操作可見,且A的執(zhí)行順序一定在B的前面。第二條是JMM對(duì)編譯器和處理器重排序的約束原則,只要不改變程序的執(zhí)行結(jié)果,編譯器和處理器怎么優(yōu)化都行。
二、組成Happens-Before模型的八種規(guī)則
happens-before模型是JMM要求多個(gè)操作必須滿足的關(guān)系模型,而happens-before模型是由八條具體規(guī)則組成的,具體如下:
- 程序順序規(guī)則(as-if-serial語義):一個(gè)線程中的每個(gè)操作,happens-before于該線程的任意后續(xù)操作。
- 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖,happens-before于隨后對(duì)這個(gè)鎖的加鎖。
- volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫,happens-before于任意后續(xù)對(duì)該volatile域的讀。
- 傳遞性規(guī)則:如果A happens-before B,且B happens-before C,那么A happens-before C。
- start()規(guī)則: 如果線程A執(zhí)行ThreadB.start(),那么A線程的ThreadB.start()操作happens-before于線程B的任意操作。
- join()規(guī)則: 如果線程A執(zhí)行ThreadB.join(),那么B線程中的任意操作happens-before于線程A從ThreadB.join()成功返回。
- 程序中斷規(guī)則:對(duì)線程interrupted()方法的調(diào)用happens-before與被中斷線程的代碼檢測(cè)到中斷時(shí)間的發(fā)生。
- 對(duì)象finalize規(guī)則:一個(gè)對(duì)象初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)happens-before于發(fā)生它的finalize()方法的開始。
2.1 程序順序規(guī)則(as-if-serial語義)
- 不能改變程序的執(zhí)行結(jié)果(在單線程環(huán)境下,執(zhí)行的結(jié)果不變)
- 依賴問題, 如果兩個(gè)指令存在依賴關(guān)系,不允許重排序
這樣聽起來好像happens-before和as-if-serial語義沒什么區(qū)別,但是一定要搞清楚as-if-serial是組成happens-before模型的其中一條規(guī)則,兩者是包含的關(guān)系,下面對(duì)兩者進(jìn)行一個(gè)比較:
- as-if-serial語義保證單線程內(nèi)程序的執(zhí)行結(jié)果不被改變,happens-before保證正確同步的多線程和單線程的執(zhí)行結(jié)果不被改變。
- as-if-serial語義給編寫單線程程序的程序員創(chuàng)造了一個(gè)幻境:單線程程序是按程序的順序來執(zhí)行的。happens-before關(guān)系給編寫正確同步的多線程程序的程序員創(chuàng)造了一個(gè)幻境:正確同步的多線程程序是按happens-before指定的順序來執(zhí)行的。
- as-if-serial語義和happens-before這么做的目的,都是為了在不改變程序執(zhí)行結(jié)果的前提下,盡可能地提高程序執(zhí)行的并行度。
int a=0;
int b=0;
void test(){int a=1; aint b=1; bint c=a*b; c
}
a happens -before b ; b happens before c
2.2 傳遞性規(guī)則
a happens-before b 且 b happens- before c,那么 a happens-before c
2.3 volatile變量規(guī)則
volatile 修飾的變量的寫操作,一定happens-before后續(xù)對(duì)于volatile變量的讀操作。該規(guī)則利用volatile關(guān)鍵字通過內(nèi)存屏障機(jī)制來防止指令重排。
上圖中的No表示不允許重排序,由上圖我們就知道了使用volatile可以保證有序性。
public class VolatileExample {int a = 0;volatile boolean flag = false;public void writer() {a = 1; 1flag = true; // 修改 2}public void reader() {if (flag) { // true 3// i的值最終一定會(huì)被設(shè)置為1int i = a; 4}}
}
1 happens-before 2 ?-> 因?yàn)槌绦蝽樞蛞?guī)則和volatile規(guī)則。
3 happens-before 4 ?-> 因?yàn)槌绦蝽樞蛞?guī)則
2 happens-before 3 ?-> 因?yàn)関olatile規(guī)則
1 happens-before 4 ?-> 因?yàn)閭鬟f性規(guī)則?
基于上面的規(guī)則,保證了最終i=1成立。
這里重點(diǎn)講一下上面的1 happens-before 2 為什么成立。在上面給出了volatile重排序規(guī)則表的圖片,其中有一條是代碼中第一個(gè)操作是普通讀寫(在上面代碼中對(duì)應(yīng)了a=1),第二個(gè)操作是volatile寫(在上面代碼中對(duì)應(yīng)了flag=true),這種情況是不允許重排序的,所以1 happens-before 2成立。
2.4 監(jiān)視器鎖規(guī)則
int x = 10;
synchronized(this) {// 后續(xù)線程讀取到的x的值一定12if(x < 12) {x = 12;}
}
x = 12;
2.5 start規(guī)則
public class StartDemo{int x = 0;Thread t1 = new Thread(()->{// 讀取x的值 一定是20if(x == 20){}});x = 20;// t1.start()以及之前的所有操作都發(fā)生在t1線程中任意操作之前t1.start();
}
2.6 Join規(guī)則
public class Test{int x=0;Thread t1=new Thread(()->{// t1線程中的任意操作都發(fā)生在當(dāng)前線程中t1.join成功返回之前x = 200;});t1.start();t1.join(); //保證結(jié)果的可見性。//在此處讀取到的x的值一定是200.
}
相關(guān)文章:?
【Java內(nèi)存模型】Java內(nèi)存模型(JMM)詳解以及并發(fā)編程的三個(gè)重要特性(原子性,可見性,有序性)
【并發(fā)編程】volatile關(guān)鍵字最全詳解,看這一篇就夠了
【并發(fā)編程】synchronized關(guān)鍵字最全詳解,看這一篇就夠了
?