該類是一個讀寫鎖的改進,它的思想是讀寫鎖中讀不僅不阻塞讀,同時也不應該阻塞寫。
讀不阻塞寫的實現思路:
在讀的時候如果發生了寫,則應當重讀而不是在讀的時候直接阻塞寫!
因爲在讀線程非常多而寫線程比較少的情況下,寫線程可能發生飢餓現象,也就是因爲大量的讀線程存在並且讀線程都阻塞寫線程,
因此寫線程可能幾乎很少被調度成功!當讀執行的時候另一個線程執行了寫,則讀線程發現數據不一致則執行重讀即可。所以讀寫都存在的情況下,
使用StampedLock就可以實現一種無障礙操作,即讀寫之間不會阻塞對方,但是寫和寫之間還是阻塞的!
程序舉例:
public class Point { //一個點的x,y座標 private double x,y; /**Stamped類似一個時間戳的作用,每次寫的時候對其+1來改變被操作對象的Stamped值 * 這樣其它線程讀的時候發現目標對象的Stamped改變,則執行重讀*/ private final StampedLock stampedLock = new StampedLock();
// an exclusively locked method void move(doubledeltaX,doubledeltaY) { /**stampedLock調用writeLock和unlockWrite時候都會導致stampedLock的stamp值的變化 * 即每次+1,直到加到最大值,然後從0重新開始 */ longstamp =stampedLock.writeLock(); //寫鎖 try { x +=deltaX; y +=deltaY; } finally { stampedLock.unlockWrite(stamp);//釋放寫鎖 } }
double distanceFromOrigin() { // A read-only method /**tryOptimisticRead是一個樂觀的讀,使用這種鎖的讀不阻塞寫 * 每次讀的時候得到一個當前的stamp值(類似時間戳的作用)*/ longstamp =stampedLock.tryOptimisticRead();
//這裏就是讀操作,讀取x和y,因爲讀取x時,y可能被寫了新的值,所以下面需要判斷 double currentX =x, currentY =y;
/**如果讀取的時候發生了寫,則stampedLock的stamp屬性值會變化,此時需要重讀, * 再重讀的時候需要加讀鎖(並且重讀時使用的應當是悲觀的讀鎖,即阻塞寫的讀鎖) * 當然重讀的時候還可以使用tryOptimisticRead,此時需要結合循環了,即類似CAS方式 * 讀鎖又重新返回一個stampe值*/ if (!stampedLock.validate(stamp)) { stamp =stampedLock.readLock(); //讀鎖 try { currentX =x; currentY =y; }finally{ stampedLock.unlockRead(stamp);//釋放讀鎖 } } //讀鎖驗證成功後才執行計算,即讀的時候沒有發生寫 return Math.sqrt(currentX *currentX + currentY *currentY); } } |
StampedLock的實現思想
在StampedLock中使用了CLH自旋鎖,如果發生了讀失敗,不立刻把讀線程掛起,鎖當中維護了一個等待線程隊列。
所有申請鎖但是沒有成功的線程都會記錄到這個隊列中,每一個節點(一個節點表示一個線程)保存一個標記位(locked),
用於判斷當前線程是否已經釋放鎖。當一個未標記到隊列中的線程試圖獲得鎖時,會取得當前等待隊列尾部的節點作爲其前序節點,
並使用類似如下代碼(一個空的死循環)判斷前序節點是否已經成功的釋放了鎖:
while(pred.locked){ }
解釋:pred表示當前試圖獲取鎖的線程的前序節點,如果前序節點沒有釋放鎖,則當前線程就執行該空循環並不斷判斷前序節點的鎖釋放,
即類似一個自旋鎖的效果,避免被系統掛起。當循環一定次數後,前序節點還沒有釋放鎖,則當前線程就被掛起而不再自旋,
因爲空的死循環執行太多次比掛起更消耗資源。