網(wǎng)絡(luò)费就、并發(fā)相關(guān)的知識(shí),相對(duì)其他一些編程知識(shí)點(diǎn)更難一些川队,主要是不好調(diào)試并且涉及內(nèi)容太多 力细!
所以今天就取一篇并發(fā)相關(guān)的內(nèi)容分享下,我相信大家認(rèn)真看完會(huì)有收獲的固额。
大家可以先看看這個(gè)問題眠蚂,看看這個(gè)是否有問題呢? 那里有問題呢斗躏?
如果你在這個(gè)問題上面停留超過5s的話逝慧,那么表示你對(duì)這塊某些知識(shí)還有點(diǎn)模糊,需要再鞏固下啄糙,下面我們一起來分析下笛臣!
1. 結(jié)論
多線程并發(fā)的同時(shí)進(jìn)行set、get操作隧饼,A線程調(diào)用set方法沈堡,B線程并不一定能對(duì)這個(gè)改變可見!Q嘌恪诞丽!
2. 分析
這個(gè)類非常簡單,里面有一個(gè)屬性贵白,有2個(gè)方法:get率拒、set方法崩泡,一個(gè)用來設(shè)置屬性值禁荒,一個(gè)用來獲取屬性值,在設(shè)置屬性方法上面加了synchronized角撞。
隱式信息:多線程并發(fā)的同時(shí)進(jìn)行set呛伴、get操作勃痴,A線程調(diào)用set方法,B線程可以里面感知到嗎热康?沛申??
說到這里姐军,問題就變成了synchronized在剛剛說的上下文下面能否保證可見性L摹!奕锌!
3. 關(guān)鍵詞synchronized的用法
指定加鎖對(duì)象:對(duì)給定對(duì)象加鎖著觉,進(jìn)入同步代碼前需要獲得給定對(duì)象的鎖。
直接作用于實(shí)例方法:相當(dāng)于對(duì)當(dāng)前實(shí)例加鎖惊暴,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖饼丘。
直接作用于靜態(tài)方法:相當(dāng)于對(duì)當(dāng)前類加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類的鎖辽话。
synchronized它的工作就是對(duì)需要同步的代碼加鎖肄鸽,使得每一次只有一個(gè)線程可以進(jìn)入同步塊(其實(shí)是一種悲觀策略)從而保證線程之間得安全性。
從這里我們可以知道油啤,我們需要分析的屬于第二類情況典徘,也就是說多個(gè)線程如果同時(shí)進(jìn)行set方法的時(shí)候,由于存在鎖益咬,所以會(huì)一個(gè)一個(gè)進(jìn)行set操作烂斋,并且是線程安全的,但是get方法并沒有加鎖础废,表示假如A線程在進(jìn)行set的同時(shí)B線程可以進(jìn)行g(shù)et操作汛骂。并且可以多個(gè)線程同時(shí)進(jìn)行g(shù)et操作,但是同一時(shí)間最多只能有一個(gè)set操作评腺。
4. Java 內(nèi)存模型 happens-before原則
JSR-133 內(nèi)存模型使用 happens-before 的概念來闡述操作之間的內(nèi)存可見性帘瞭。在 JMM 中,如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見蒿讥,那么這兩個(gè)操作之間必須要存在 happens-before 關(guān)系蝶念。這里提到的兩個(gè)操作既可以是在一個(gè)線程之內(nèi),也可以是在不同線程之間芋绸。
與程序員密切相關(guān)的 happens-before 規(guī)則如下:
程序順序規(guī)則:一個(gè)線程中的每個(gè)操作媒殉,happens-before 于該線程中的任意后續(xù)操作。
監(jiān)視器鎖規(guī)則:對(duì)一個(gè)監(jiān)視器的解鎖摔敛,happens-before 于隨后對(duì)這個(gè)監(jiān)視器的加鎖廷蓉。
volatile 變量規(guī)則:對(duì)一個(gè) volatile 域的寫,happens-before 于任意后續(xù)對(duì)這個(gè) volatile 域的讀马昙。
傳遞性:如果 A happens-before B桃犬,且 B happens-before C刹悴,那么 A happens-before C。
注意攒暇,兩個(gè)操作之間具有 happens-before 關(guān)系土匀,并不意味著前一個(gè)操作必須要在后一個(gè)操作之前執(zhí)行!happens-before 僅僅要求前一個(gè)操作(執(zhí)行的結(jié)果)對(duì)后一個(gè)操作可見形用,且前一個(gè)操作按順序排在第二個(gè)操作之前(the first is visible to and ordered before the second)就轧。
其中有監(jiān)視器鎖規(guī)則:對(duì)一個(gè)監(jiān)視器的解鎖,happens-before 于隨后對(duì)這個(gè)監(jiān)視器的加鎖田度。這一條钓丰,僅僅只是針對(duì)synchronized的set方法,而對(duì)于get并沒有這方面的說明每币。
其實(shí)在這種上下文下面一個(gè)synchronized的set方法携丁,一個(gè)普通的get方法,a線程調(diào)用set方法兰怠,b線程并不一定能對(duì)這個(gè)改變可見梦鉴!
5. volatile
volatile可見性
前面happens-before原則就提到:volatile 變量規(guī)則:對(duì)一個(gè) volatile 域的寫,happens-before 于任意后續(xù)對(duì)這個(gè) volatile 域的讀揭保。volatile從而保證了多線程下的可見性7食取!秸侣!
volatile 禁止內(nèi)存重排序
下面是 JMM 針對(duì)編譯器制定的 volatile 重排序規(guī)則表:
為了實(shí)現(xiàn) volatile 的內(nèi)存語義存筏,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序味榛。
下面是基于保守策略的 JMM 內(nèi)存屏障插入策略:
在每個(gè) volatile 寫操作的前面插入一個(gè) StoreStore 屏障椭坚。
在每個(gè) volatile 寫操作的后面插入一個(gè) StoreLoad 屏障。
在每個(gè) volatile 讀操作的后面插入一個(gè) LoadLoad 屏障搏色。
在每個(gè) volatile 讀操作的后面插入一個(gè) LoadStore 屏障善茎。
下面是保守策略下,volatile 寫操作 插入內(nèi)存屏障后生成的指令序列示意圖:
下面是在保守策略下频轿,volatile 讀操作 插入內(nèi)存屏障后生成的指令序列示意圖:
上述 volatile 寫操作和 volatile 讀操作的內(nèi)存屏障插入策略非常保守垂涯。在實(shí)際執(zhí)行時(shí),只要不改變 volatile 寫-讀的內(nèi)存語義航邢,編譯器可以根據(jù)具體情況省略不必要的屏障耕赘。
雙重檢查鎖實(shí)現(xiàn)單例中就需要用到這個(gè)特性!I乓蟆操骡!
6. 模擬
通過上面的分析,其實(shí)這個(gè)題目涉及到的內(nèi)容都提到了,并且進(jìn)行了解答当娱。
雖然你知道的原因吃既,但是想模擬并不是一件容易的事情考榨!跨细,下面我們來模擬看看效果:
public class ThreadSafeCache {
int result;
public int getResult() {
return result;
}
public synchronized void setResult(int result) {
this.result = result;
}
public static void main(String[] args) {
ThreadSafeCache threadSafeCache = new ThreadSafeCache();
for ( int i = 0 ; i < 8 ; i++) {
new Thread (() -> {
int x = 0 ;
while (threadSafeCache.getResult() < 100) {
x++;
}
System.out.println(x);
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadSafeCache.setResult(200);
}
}
效果:
程序會(huì)一直卡在這邊不動(dòng),表示set修改的200河质,get方法并不可見<讲选!掀鹅!
添加volatile 關(guān)鍵詞觀察效果
其實(shí)例子中synchronized關(guān)鍵字可以去掉散休,僅僅用volatile即可。
效果:
代碼很快正常結(jié)束了乐尊!
結(jié)論: 線程并發(fā)的同時(shí)進(jìn)行set戚丸、get操作,A線程調(diào)用set方法扔嵌,B線程并不一定能對(duì)這個(gè)改變可見O薷!痢缎!胁勺,上面的代碼中,如果對(duì)get方法也加synchronized也是可見的独旷,還是happens-before的 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)監(jiān)視器的解鎖署穗,happens-before 于隨后對(duì)這個(gè)監(jiān)視器的加鎖。 只是volatile比synchronized更輕量級(jí)嵌洼,所以本例直接用volatile案疲。但是對(duì)于符合非原子操作i++這里還是不行的還是需要synchronized。