天天看點

并發程式設計的藝術學習筆記 -基礎

一:基本基礎

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類被初始化

}

}

繼續閱讀