1. 程式人生 > 程式設計 >Java併發程式設計(二)——併發級別

Java併發程式設計(二)——併發級別

由於臨界區的存在,多執行緒之間的併發必須受到控制。根據控制併發的策略,可以把併發分為幾個級別:阻塞、無飢餓、無障礙、無鎖、無等待。

1.阻塞

當一個執行緒等待由其他執行緒佔有的資源,並且在資源被釋放之前當前執行緒無法繼續執行,這時我們稱這個執行緒為“阻塞”的。當我們使用synchronized關鍵字或重入鎖時(這兩種技術將在後續文章中介紹),得到的就是阻塞的執行緒。
阻塞的執行緒在執行後續程式碼前會嘗試得到臨界區的鎖,如果嘗試失敗,執行緒將會被掛起處於等待狀態,直到搶佔到所需資源為止。

2.無飢餓

執行緒是有優先順序的區分的,當執行緒排程的時候總會傾向於優先將資源分配給高優先順序的執行緒。也就是說,對同一個資源的分配是不公平的。下圖顯示了非公平與公平兩種情況。

對非公平鎖來說,系統允許高優先順序的執行緒插到低優先順序執行緒之前執行,這就可能會導致低優先順序執行緒一直得不到執行,即產生了飢餓。但如果是公平鎖,系統將會按先來後到的順序依次執行執行緒,不管執行緒優先順序有多高都必須排隊,這樣所有的執行緒都有機會執行,也就是無飢餓

3.無障礙

無障礙是一種最弱的非阻塞排程,執行緒之間如果是無障礙執行的話,那麼他們不會因為臨界區的問題導致被掛起。在無障礙的情況下,為了保證臨界區中的共享資料不被破壞,執行緒需要檢測共享資料是否完整,如果資料被破壞,執行緒就會對自己所做的修改進行回滾,確保資料安全。
阻塞的控制方式屬於悲觀策略,即系統認為多個執行緒之間很有可能對資源發生爭搶,因此選擇保護共享資料為第一優先順序。反觀非阻塞排程就屬於樂觀策略,系統認為多個執行緒之間發生衝突的可能性很小,因此多執行緒應該無障礙的執行,當檢測到衝突時再進行回滾。
但是當臨界區存在嚴重的衝突時,可能會發生所有執行緒不斷回滾自己的操作,而沒有一個執行緒可以走出臨界區,這種情況會影響系統的正常執行。

4.無鎖

在上述無障礙的場景中,有可能發生執行緒無法退出臨界區的情況,而無鎖就是一種無障礙策略的實現,在無障礙的基礎上添加了一個條件:保證必然有一個執行緒能夠在有限步內完成操作離開臨界區。
在無鎖的呼叫中,一個典型的特點是可能會包含一個無限迴圈。在迴圈中執行緒會不斷嘗試修改共享變數。如果沒有衝突則修改成功,執行緒走出臨界區;如果遇到衝突則重試修改操作。但無論如何,無鎖演演算法總能保證有一個執行緒可以成功。
Java中提供了一種無鎖演演算法:Compare-And-Swap(CAS),由於計算機在CPU層面上支援CAS的原子操作,所以當多執行緒爭相修改共享資源時必然會有一個執行緒修改成功。但當競爭非常激烈時,某些“運氣不佳”的執行緒也會出現飢餓的情況。

while (!atomicVar.compareAndSet(localVar,localVar+1)) {
    localVar = atomicVar.get();
}
複製程式碼

5.無等待

無鎖只要求有一個執行緒可以在有限步內完成操作,而無等待則是在無鎖的基礎上進一步擴充套件:它要求所有的執行緒都在有限步內完成操作。這樣就不會引發飢餓問題。
一種典型的無等待結構就是RCU(Read-Copy-Update)。它的基本思想是對資料的讀可以不加控制。因此所有的讀執行緒都是無等待的。當寫資料時,先取得原始資料的副本,接著只修改副本資料,等待合適的時機將副本資料回寫到原始資料。