《JAVA併發程式設計實戰》原子變數和非阻塞同步機制

2021-08-30 13:22:04 字數 2787 閱讀 6033

原子變數類

非阻塞演算法

即使原子變數沒有用於非阻塞演算法的開發,他們也可以用作一種「更好的」volatile型別變數。原子變數提供了與volatile型別變數相同的記憶體語義,此外還支援原子的更新操作,從而使他們更加適用於實現計數器、序列發生器和統計資料收集等,同時還能比基於鎖的方法提供更高的可伸縮性。

通過使用一致的鎖定協議來協調對共享狀態的訪問,可以確保無論哪個執行緒持有守護變數的鎖,都能採用獨佔方式來訪問這些變數,並且對變數的任何修改對隨後獲得這個鎖的其他執行緒都是可見的。

如果多個執行緒同時請求鎖,那麼一些執行緒將被掛起並且在稍後恢復執行。當執行緒恢復執行時,必須等待其他執行緒執行完他們的時間片以後,才能被排程執行。在掛起和恢復執行緒等過程中存在著很大的開銷,並且通常存在著較長時間的中斷。

volatile通過較輕量級的同步機制,因為在使用這些變數時不會發生上下文切換或執行緒排程等操作。然而,volatile變數只能保證可見性,而不能保證原子性。

鎖定還有一些缺點,主要表現在上下文切換,排程延遲等。

獨佔式是一項悲觀技術——它假設了最壞的情況(如果你不鎖門,你就會被偷),並且只有在確保其他執行緒不會造成干擾的情況下才能執行下去。

一種樂觀的方法:通過這種方法可以在不發生干擾的情況下完成更新操作。這種方法需要借助衝突檢查機制來判斷在更新過程中是否存在來自其他執行緒的干擾,如果存在,則這個操作將失敗並且可以重試。

現代處理器支援了一些原子的寫指令:比較並交換(compare and swap)或者關聯載入/條件儲存。

cas包含了3個運算元——需要讀寫的記憶體為止v、進行比較的值a、擬寫入的新值b。當且僅當v的值等於a時,cas才會通過原子方式用新值b來更新v,否則不執行任何操作。

模擬cas

public

class

simulatedcas

public

synchronized

intcompareandswap

(int expectedvalue,

int newvalue)

return oldvalue;

}public

synchronized

boolean

compareandset

(int expectedvalue,

int newvalue)

}

public

class

cascounter

public

intincrement()

while

(v != value.

compareandswap

(v,v+1)

);return v+1;

}}

當競爭不高時,基於cas的計數器在效能上遠超過了基於鎖的計數器,而在沒有競爭的時候更高。

cas的主要缺點是,它將使呼叫者處理競爭問題(通過重試、回退、放棄),而在鎖中能自動處理競爭問題(執行緒在獲得鎖之前一直阻塞)。

12個原子變數類,分為4組;

標量類:atomicinteger,atomicelong,atomicboolean,atomicreference

更新器類

陣列類復合變數類

public

class

casnumberrange..

.}private

final atomicreference

values =

newatomicreference

(new

intpair(0

,0))

;public

intgetlower()

public

intgetupper()

public

void

setlower

(int i)

intpair newv =

newintpair

(i,oldv.upper);if

(values.

compareandset

(oldv,newv))}

}...

}

如果在某種演算法中,乙個執行緒的失敗或掛起不會導致其他執行緒也失敗或掛起,那麼這種演算法被稱為非阻塞演算法。如果在演算法的每個步驟中都存在某個執行緒能夠執行下去,那麼這種演算法也被稱為無鎖演算法。

建立非阻塞演算法的關鍵在於,找出如何將原子修改的範圍縮小到單個變數上,同時還要維護資料一致性。

使用trebier演算法構造的非阻塞棧:

public

class

concurrentstack

while

(!top.

compareandset

(oldhead,newhead));

}public e pop()

newhead = oldhead.next;

}while

(!top.

compareandset

(oldhead,newhead));

return oldhead.item;

}private

static

class

node

}}

鏈結需要維護頭指標和尾指標。問題在於如何同時維護這兩個變數,避免一致性遭到破壞。

第乙個技巧是,即使在乙個包含多個步驟的更新操作中,也要確保資料結構總是處於一致的狀態。第二個技巧是,如果當b到達時發現a正在修改資料結構,那麼資料結構中應該有足夠多的資訊,使得b能完成a的更新操作。

乙個值由a變為b,然後在變為a。引入版本號。

java併發程式設計基礎 原子類與原子更新

1 什麼是原子類 原子類可以認為其操作都是不可分割 2 為什麼要有原子類 對多執行緒訪問同乙個變數,我們需要加鎖,而鎖是比較消耗效能的,jdk1.5之後,新增的原子操作類提供了一種用法簡單 效能高效 執行緒安全地更新乙個變數的方式,這些類同樣位於juc包下的atomic包下,發展到jdk1.8,該包...

Java併發程式設計實戰 總結

1.可變狀態是至關重要的。所有的併發問題都可以歸結為如何協調對併發狀態的訪問,可變狀態越少,就越容易確保執行緒安全性。2.盡量將域宣告為final型別,除非需要它們是可變的。3.不可變物件一定是執行緒安全的。不可變物件能極大地降低併發程式設計的複雜性。它們更為簡單而且安全,可以任意共享而無須使用加鎖...

java併發程式設計實戰 簡介

1 併發和並行的區別 併發 多個程式在同一時間段執行,只有乙個cpu。並行 多個程式在同一時刻執行,有多個cpu。2 執行緒帶來的風險 1 安全性問題 永遠不發生糟糕的事情 兩個或多個執行緒同時訪問乙個變數,可能會出現差錯。例如,兩個執行緒同時對乙個變數執行讀操作,從而它們得到了相同的值,違背了該變...