併發編程的藝術學習筆記 -基礎

一:基本基礎

1,上下文切換

cpu不停的切換線程執行。那麼就有一個問題,讓出cpu的線程等下次輪到自己佔有cpu的時,如何知道自己之前運行到了哪裏,所以在切換上下文時需要保存當前線程的執行現場,當再次執行時根據保存的執行現場信息恢復執行現場。

線程上下文切換的時機:

當前線程的cpu時間片使用完處於就緒狀態時,當前線程被其他線程中斷時。

減少上下文切換:有無鎖併發編程,cas算法,使用最少的線程和使用協程。

2,volatile

volatile是輕量級的synchronized。

定義:java編程語言允許線程訪問共享變量,爲了確保共享變量能被準確和一致的更新,線程應該確保通過排他鎖單獨獲得這個變量,java提供了volatile,在某些情況下比鎖更加方便,如果一個字段被聲明成volatile,java線程內存模型確保所有線程看到這個變量的值是一致的。

實現原則:

1,lock前綴指令會引起處理器緩存回寫到內存。

2,一個處理器的緩存回寫到內存會導致其他處理器的緩存無效。

3,synchronized鎖的狀態:無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,鎖可以升級但不能降級。

 

 

二:java內存模型
兩個關鍵性問題:線程之間如何通信,線程之間如何同步
通信機制:共享內存和消息傳遞
Java的併發採用的是共享內存模型。
Java線程之間的通信由Java內存模型控制。
線程與主內存之間的抽象關係:線程之間的共享變量存儲在主內存中,每個線程都有一個私有的本地內存,本地內存存儲了該線程讀寫共享變量的副本。本地內存是一個抽象的概念。
重排序
在執行程序時,爲了提高性能,編譯器和處理器常常會對指令重排序。
三種重排序:
1,編譯器優化的重排序
2,指令級並行的重排序
3,內存系統的重排序

happens-before 簡介
與程序員密切相關的happens-before規則如下:
程序順序規則:一個線程中的每個操作,happens-before與該線程中任意後續操作
監視器鎖規則:對一個鎖的解鎖,happens-before與隨後對這個鎖的加鎖
volatile變量規則:對一個volatile域的寫,happens-before與任意後續對這個volatile域的讀。
傳遞性:a-b,b-c,所有a-c。
注意:兩個操作之間有happens-before關係,並不意味着前一個操作必須要在後一個操作之前執行,happens-before僅僅要求前一個操作的結果對後一個操作可見。且前一個操作按順序排在第二個操作之前。


as-if-serial語義
不管怎麼重排序,(單線程)程序的執行結果不能改變。重排序必須遵守這個語義。
編譯器和處理器不會對存在數據依賴關係的操作(讀寫,寫寫,寫讀)做重排序,因爲會改變執行結果。

在單線程程序中,對存在控制依賴的操作重排序,不會改變執行結果,但在多線程中,對存在控制依賴的操作重排序,會改變程序的執行結果。
順序一致性模型(理論化參考模型)
數據競爭與順序一致性:
當程序未正確同步時,就可能會存在數據競爭,java內存模型規範對數據競爭的定義是:
在一個線程中寫一個變量,在另外一個線程讀同一個變量,而且寫和讀沒有通過同步來排序。
兩個特性:
一個線程中所有操作必須要按照程序的順序來執行
所有線程都只能看到一個單一的操作執行順序,在順序一致性內存模型中,每個操作都必須原子執行且立刻對所有線程可見。

對於未同步或未正確同步的多線程程序,jmm只提供最小的安全性:線程執行時讀取到的值,要麼是之前某個線程寫入的值,要麼就是默認值,jmm保證線程讀取到的值不會無中生有。
jmm不保證未同步程序的執行結果與該線程在順序一致性模型中的執行結果一致。
未同步程序在jmm模型順序一致性模型中的差異:
1,順序一致性模型保證單線程內的操作會按程序的順序執行,而jmm不保證單線程內的操作會按順序執行。
2,順序一致性模型保證所有線程只能看到一致的操作執行順序,而jmm不保證所有線程能看到一致的操作執行順序。
3,jmm不保證對64位的long型和double型的寫操作具有原子性,而順序一致性模型保證對所有內存讀寫具有原子性。

volatile的內存語義
鎖的happens-before規則保證了釋放鎖和獲取鎖的兩個線程之間的內存可見性,這意味着對一個volatile變量的讀,總是能看到對這個vilatile變量最後的寫入。
volatile特性:可見性,原子性。
volatile變量的寫-讀可以實現線程之間的通信
從內存語義的角度來說,volatile的寫-讀與鎖的釋放-獲取友相愛那共同被告的內存效果:volatile寫和鎖的釋放有相同的內存語義,volatile讀與鎖的獲取有相同的內存語義。
3.5 鎖的內存語義
鎖事java併發編程中最重要的同步機制,鎖除了讓臨界區互斥執行外,還可以讓釋放鎖的線程向獲取同一個鎖的線程發送消息。
對比鎖釋放-獲取的內存語義與volatile寫-讀內存語義:鎖釋放與volatile寫有相同的語義,鎖獲取與volatile讀有相同的內存語義。
總結:線程a釋放一個鎖,實質上是a發出一個消息給下一個線程。
線程b獲取一個鎖,實質上是b接受了之前某個線程的消息。
a釋放,b獲取,實質上是a通過主內存向b發送消息。
3.6 final域的內存語義
1,final的重排序規則
對於final,編譯器和處理器都要遵守兩個重排序規則:
1:在構造函數內對一個final域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。
2:初次讀一個包含final域的對象的引用,與隨後初次讀這個final域,這兩個操作之間不能重排序。
2,寫final域的重排序規則
寫final域的重排序規則禁止把final域的寫的重排序到構造函數之外。
確保:在對象引用爲任意線程可見之前,對象的final域已經正確初始化過了,而普通域不具有這個保障。
3,讀final域的重排序規則
讀final域的重排序規則是:在一個線程中,初次讀對象引用與初次讀該對象包含的final域,jmm禁止處理器重排序這兩個操作。當然這個規則僅僅針對處理器,編譯器會在讀final域之前插入一個loadload屏障。
確保:在讀一個對象的final域之前,一定會先讀包含這個final域的對象的引用。
總結:個人理解就是,我們常常在類中定義一些固定變量用final,這樣在這個類中引用這個變量就是我們常說不變的。
4,final域如果是一個對象呢,final域重排序對編譯器和處理器加了約束:在構造函數內對一個final引用的對象成員域(對象中的一個成員,字段或者數組中的一個值)的寫入,與隨後在構造函數外把這個被構造對象的引用賦值給一個引用變量,這兩個操作不能重排序。
5,還需要一個保證:在構造函數內不,不能讓這個被構造對象的引用爲其他線程所見,也就是不能溢出。
3.7 jmm設計
基本原則:只要不改變程序的執行結果,編譯器和處理器怎麼優化都行。
jsr-33使用happens-before的概念來指定兩個操作(不管是一個線程還是不同線程)的執行順序,通過happens-before關係向程序員提供跨線程的內存可見性保證。
定義:
1:如果一個操作happens-bedore另外一個操作,那麼第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
2:兩個操作之間存在happens-before關係,並不意味着java 平臺的具體實現必須按照happens-before關係指定的順序來執行,如果結果一致,那麼重排序也可以。
規則:
1:程序順序規則:一個線程中的每個操作,happens-before與該線程中的任意後續操作。
2:監視器規則:對一個鎖的解鎖,happens-before與隨後對這個鎖的加鎖
3:volatile變量規則:對一個volatile域的寫,h-b與任意後續對於這個volatile域的讀
4:傳遞性:a-b,b-c,那麼a-c
5:start()規則:如果線程a執行操作ThreadB.start()(啓動線程b),那麼a線程的ThreadB.start()操作h-b與線程b中的任意操作。
6:join()規則:如果線程a執行操作ThreadB.join()併成功返回,那麼線程b中的任意操作h-b於線程a從htreadB.join()操作成功返回。
3.8 雙重檢查鎖定與延遲初始化
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問題根源出在這裏
}
}
return instance;
}
}
}
8.1 基於volatile的解決方案(禁止重排序)
public class DoubleCheckedLocking {                               
private volatile static Instance instance;                            
public static Instance getInstance () {                          
if(instance == null) {                                               
synchronized (DoubleCheckedLocking.class) {     
if(instance == null){                                   
instance == new Instance();                      //instance爲volatile,
}
}
return instance;
}
}
}
8.4 基於類初始化的解決方案(允許重排序,但是其他線程看不到)
public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}
public static Instance getInstance() {
return InstanceHolder.instance;    //這裏將導致InstanceHolder類被初始化
}
}
 




 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章