多執行緒(一 併發理論基礎,可見性 原子性和有序性)

2021-10-20 22:02:20 字數 1634 閱讀 3763

單核時代所有的執行緒都在一顆cpu上執行,cpu快取與記憶體一致性容易解決。所有執行緒都操作同一顆cpu的快取,乙個執行緒對快取的寫,對另乙個執行緒來說是可見的。乙個執行緒對共享變數的修改,另乙個執行緒立馬可見,這就是可見性。

多核時代每個cpu都有自己的快取,多個執行緒在不同的cpu上執行時,這些執行緒操作的是不同的cpu快取,這時執行緒之間的變數就不具備可見性了。

public class test 

} public static long calc() );

thread th2 = new thread(()->);

// 啟動兩個執行緒

th1.start();

th2.start();

// 等待兩個執行緒執行結束

th1.join();

th2.join();

return count;

}}

如上**,兩個執行緒去呼叫add方法。我們假設執行緒 a 和執行緒 b 同時開始執行,那麼第一次都會將 count=0 讀到各自的 cpu 快取裡,執行完 count+=1 之後,各自 cpu 快取裡的值都是 1,同時寫入記憶體後,我們會發現記憶體中是 1,而不是我們期望的 2。之後由於各自的 cpu 快取裡都有了 count 的值,兩個執行緒都是基於 cpu 快取裡的 count 值來計算,所以導致最終 count 的值都是小於 20000 的。這就是快取的可見性問題。

乙個或者多個操作在cpu執行過程中不會被中斷的特性是原子性。

早起的作業系統是基於程序來排程cpu的,不同程序間是不共享記憶體空間的,程序要做任務切換就要切換記憶體對映位址。但是執行緒裡建立的所有執行緒都是共享同乙個記憶體空間的,所以執行緒做切換成本較低。現在的作業系統都是基於執行緒來排程。

有序性指的是程式按照**的先後順序執行,編譯器為了優化效能,有時候會改變程式中語句的先後順序,雖然調整了順序但是不影響程式的最終結果。但是有時候會出現意想不到的bug。

乙個經典的例子:雙重檢查建立單例物件。

public class singleton 

}return instance;

}}

兩個執行緒同時呼叫如上singleton方法。完美的情況是,執行緒ab同時呼叫getinstance方法,他們會發現instance==null,然後synchronized加鎖,保證只有乙個執行緒加鎖成功。執行緒a建立例項,然後釋放鎖,接著喚醒執行緒b,b加鎖進入臨界資源,發現有物件存在了,直接返回instance例項。

如上是最好的情況,但是如果發生了編譯器重排序,比如

1 分配了一塊記憶體m;   2 在記憶體m上初始化singleton物件;     3 然後m的位址賦值給instance變數。

但是實際情況是   1 分配了一塊記憶體m;  2 然後m的位址賦值給instance變數;    3 在記憶體m上初始化singleton物件;

執行緒a先去執行getinstance方法,當完成上面指令2時發生執行緒切換,執行緒b也執行getinstance方法,執行緒b執行第乙個instance == null時發現instance !=null。直接返回了instance。但是此時instance並沒有初始化過,此時訪問instance就會觸發空指標異常。

Java多執行緒可見性(一)

一 記憶體可見性 執行緒對共享變數的修改,可以及時的被其他執行緒看到。那何為共享變數呢?就是在多個執行緒的工作記憶體中存在 如下圖所示 所謂的共享變數就是主記憶體中名為s的變數,程式中所有的變數都會儲存在主記憶體中 其他執行緒也會有自己的工作記憶體,此工作記憶體的作用是為執行緒與主記憶體之間建立橋梁...

多執行緒學習一 可見性 原子性和有序性

在單核時代,所有的快取都操作同乙個cup上的快取,所以可見性很容易解決。當a執行緒更新了快取上的變數,那麼在b執行緒去訪問該變數的時候,拿到的一定是最新值。在多核時代,每個cup都有自己的快取區,當不同cup上的執行緒去訪問記憶體中的同個變數時,假設該變數在cup中都有快取。那麼不同cup上的執行緒...

c 併發程式設計基礎(一) 併發 並行域多執行緒

正文 c 11標準在標準庫中為多執行緒提供了元件,這意味著使用c 編寫與平台無關的多執行緒程式成為可能,而c 程式的可移植性也得到了有力的保證。另外,併發程式設計可提高應用的效能,這對對效能錙銖必較的c 程式設計師來說是值得關注的。回到頂部 併發指的是兩個或多個獨立的活動在同一時段內發生。生活中併發...