并發(fā)編程模型
-
1.兩個關鍵問題
-
1)線程之間如何通信
共享內(nèi)存
程之間共享程序的公共狀態(tài)宵距,通過寫-讀內(nèi)存中的公共狀態(tài)進行隱式通信
消息傳遞
程之間沒有公共狀態(tài)蒜胖,線程之間必須通過發(fā)送消息來顯式進行通信
-
2)線程之間如何同步
線程之間沒有公共狀態(tài),線程之間必須通過發(fā)送消息來顯式進行通信
總結(jié):Java的并發(fā)采用的是共享內(nèi)存模型歧蒋,Java線程之間的通信總是隱式進行醉锄,整個通信過程對程序員完全透明。
-
2.抽象結(jié)構(gòu)
-
1)本地內(nèi)存
- 每個線程都有一個私有的本地內(nèi)存(LocalMemory)龙亲,本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本陕凹。本地內(nèi)存是JMM的一個抽象概念,并不真實存在鳄炉。它涵蓋了緩存杜耙、寫緩沖區(qū)、寄存器以及其他的硬件和編譯器優(yōu)化
-
2)主內(nèi)存
- 線程之間的共享變量存儲在主內(nèi)存
附圖:
注:所有實例域拂盯、靜態(tài)域和數(shù)組元素都存儲在堆內(nèi)存中佑女,堆內(nèi)存在線程之間共享(本章用“共享變量”這個術(shù)語代指實例域,靜態(tài)域和數(shù)組元素)。局部變量(Local Variables)团驱,方法定義參數(shù)(Java語法規(guī)范稱之為Formal Method Parameters)和異常處理器參數(shù)(Exception HandlerParameters)不會在線程之間共享摸吠,它們不會有內(nèi)存可?性問題,也不受內(nèi)存模型的影響嚎花。
-
3.重排序
- 定義:
重排序是指編譯器和處理器為了優(yōu)化程序性能而對指令序列進行重新排序的一種手段寸痢。
-
1) 3種類型
- 編譯器優(yōu)化的重排序(處理器重排序)
- 編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序
- 指令級并行的重排序(處理器重排序)
現(xiàn)代處理器采用了指令級并行技術(shù)(Instruction-Level Parallelism紊选,ILP)來將多條指令重疊執(zhí)行啼止。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應機器指令的執(zhí)行順序
內(nèi)存系統(tǒng)的重排序
由于處理器使用緩存和讀/寫緩沖區(qū)兵罢,這使得加載和存儲操作看上去可能是在亂序執(zhí)行
-
2)數(shù)據(jù)依賴性
- 說明:如果兩個操作訪問同一個變量献烦,且這兩個操作中有一個為寫操作,此時這兩個操作之間就存在數(shù)據(jù)依賴性卖词。
-
存在三種類型(只要重排兩個操作執(zhí)行順序巩那,結(jié)果便會被改變)
- 寫后讀:寫一個變量之后,再讀這個變量
- 寫后寫:寫一個變量之后此蜈,在寫這個變量
- 讀后寫:讀一個變量之后拢操,再寫這個變量
-
3)as-if-serial語義
- 說明:不管怎么重排序(編譯器和處理器為了提高并行度),(單線程)程序的執(zhí)行結(jié)果不能被改變舶替。編譯器令境、runtime和處理器都必須遵守as-if-serial語義。
- 順序規(guī)則:A?happens-before B,B?happens-before C,happens-before C
那么實際執(zhí)行是B可以排在A前
顾瞪,JMM允許這種排序舔庶。
- 順序規(guī)則:A?happens-before B,B?happens-before C,happens-before C
-
4)對多線程的影響
- 對于存證控制依賴的操作重排序,可能會改變程序的執(zhí)行結(jié)果陈醒。
-
5)DCL問題(double check lock)
public class DoubleCheckedLocking { // 1
private static Instance instance; // 2
public static Instance getInstance() { // 3
if (instance == null) { // 4:第一次檢查
synchronized (DoubleCheckedLocking.class) { // 5:加鎖
if (instance == null) // 6:第二次檢查
instance = new Instance(); // 7:問題的根源出在這里
} // 8
} // 9
return instance; // 10
} // 11
}
-
那么問題就出現(xiàn)在線程執(zhí)行到第4行惕橙,代碼讀取到instance不為null時,instance引用的對象有可能還
沒有完成初始化钉跷。- __根源:
memory = allocate();? //1:分配對象的內(nèi)存空間
ctorInstance(memory); //2:初始化對象
instance = memory;?? //3:設置instance指向剛分配的內(nèi)存地址
- JMM允許上述命令的執(zhí)行順序調(diào)整為
memory = allocate();? //1:分配對象的內(nèi)存空間
instance = memory;?? //3:設置instance指向剛分配的內(nèi)存地址
//注意弥鹦,此時對象還沒有被初始化!
ctorInstance(memory); //2:初始化對象
- 問題:為什么要調(diào)整這個順序呢爷辙?
-
原因:這個重排序在沒有改變單線程程序執(zhí)行結(jié)果的前提下彬坏,可以提高程序的執(zhí)行性能。
image
image
-
解決方案
- 不允許2和3重排序([
volatile
]); - 允許2和3重排序膝晾,但不允許其他線程“看到”這個重排序栓始。
- 不允許2和3重排序([
第一種基于
volatile
方案
public class SafeDoubleCheckedLocking {
private volatile static Instance instance;
public static Instance getInstance() {
if (instance == null) {
synchronized (SafeDoubleCheckedLocking.class) {
if (instance == null)
instance = new Instance(); // instance為volatile,現(xiàn)在沒問題了
}
}
return instance;
}
}
- 第二種基于類初始化方案
public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}
public static Instance getInstance() {
return InstanceHolder.instance;??// 這里將導致InstanceHolder類被初始化血当,存在初始化鎖幻赚,拿不到的線程會一直等待
}
}
-
4.happens-before
happens-before是JMM最核心的概念
1) 關系的定義
- 如果?個操作happens-before另?個操作禀忆,那么第一個操作的執(zhí)行結(jié)果將對第二個操作可見,而且第一個操作的執(zhí)行順序排在第二個操作之前落恼。[
JMM對程序員的承諾
] - 兩個操作之間存在happens-before關系箩退,并不意味著Java平臺的具體實現(xiàn)必須要按照happens-before關系指定的順序來執(zhí)行。如果重排序之后的執(zhí)行結(jié)果佳谦,與按happens-before關系來執(zhí)行的結(jié)果一致戴涝,那么這種重排序并不非法(也就是說,JMM允許這種重排序)[
JMM對編譯器和處理器重排序的約束原則
]
2) 規(guī)則
- 程序順序規(guī)則:一個線程中的每個操作吠昭,happens-before于該線程中的任意后續(xù)操作。
- 監(jiān)視器鎖規(guī)則:對一個鎖的解鎖胧瓜,happens-before于隨后對這個鎖的加鎖矢棚。
- volatile變量規(guī)則:對一個volatile域的寫,happens-before于任意后續(xù)對這個volatile域的讀府喳。
- 傳遞性:A happens-before B蒲肋,且B happens-before C,那么A happens-before C钝满。
- start()規(guī)則:如果線程A執(zhí)行操作ThreadB.start()(啟動線程B)兜粘,那么A線程的ThreadB.start()操作happens-before于線程B中的任意操作。
- join()規(guī)則:如果線程A執(zhí)行操作ThreadB.join()并成功返回弯蚜,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回孔轴。
注意:兩個操作之間具有happens-before關系,并不意味著前
一個操作必須要在后一個操作之前執(zhí)行碎捺!happens-before僅僅要求前一個操
作(執(zhí)行的結(jié)果)對后一個操作可見路鹰,且前一個操作按順序排在第一個操
作之前(the first is visible to and ordered before the second)。
-
appens-before與JMM的關系
image
話外語:
- as-if-serial語義保證單線程內(nèi)程序的執(zhí)行結(jié)果不被改變收厨,happens-before關系保證正確同步的多線程程序的執(zhí)行結(jié)果不被改變
- as-if-serial語義和happens-before這么做的目的晋柱,都是為了在不改變程序執(zhí)行結(jié)果的前提下,盡可能地提高程序執(zhí)行的并行度