StampedLock是Java8引入的一種新的所機制,簡單的理解,可以認為它是讀寫鎖的一個改進版本,讀寫鎖雖然分離了讀和寫的功能,使得讀與讀之間可以完全並發,但是讀和寫之間依然是沖突的,讀鎖會完全阻塞寫鎖,它使用的依然是悲觀的鎖策略.如果有大量的讀線程,他也有可能引起寫線程的飢餓
而StampedLock則提供了一種樂觀的讀策略,這種樂觀策略的鎖非常類似於無鎖的操作,使得樂觀鎖完全不會阻塞寫線程
- StampedLock的使用實例
public class Point { private double x, y;//內部定義表示坐標點 private final StampedLock s1 = new StampedLock();//定義了StampedLock鎖, void move(double deltaX, double deltaY) { long stamp = s1.writeLock();//這里的含義和distanceFormOrigin方法中 s1.readLock()是類似的 try { x += deltaX; y += deltaY; } finally { s1.unlockWrite(stamp);//退出臨界區,釋放寫鎖 } } double distanceFormOrigin() {//只讀方法 long stamp = s1.tryOptimisticRead(); //試圖嘗試一次樂觀讀 返回一個類似於時間戳的郵戳整數stamp 這個stamp就可以作為這一個所獲取的憑證 double currentX = x, currentY = y;//讀取x和y的值,這時候我們並不確定x和y是否是一致的 if (!s1.validate(stamp)) {//判斷這個stamp是否在讀過程發生期間被修改過,如果stamp沒有被修改過,責任無這次讀取時有效的,因此就可以直接return了,反之,如果stamp是不可用的,則意味着在讀取的過程中,可能被其他線程改寫了數據,因此,有可能出現臟讀,如果如果出現這種情況,我們可以像CAS操作那樣在一個死循環中一直使用樂觀鎖,知道成功為止 stamp = s1.readLock();//也可以升級鎖的級別,這里我們升級樂觀鎖的級別,將樂觀鎖變為悲觀鎖, 如果當前對象正在被修改,則讀鎖的申請可能導致線程掛起. try { currentX = x; currentY = y; } finally { s1.unlockRead(stamp);//退出臨界區,釋放讀鎖 } } return Math.sqrt(currentX * currentX + currentY * currentY); } }
- StampedLock的小陷阱
- 有關StampedLock的實現思想
StampedLock的內部實現是基於CLH鎖的,CLH鎖是一種自旋鎖,它保證沒有飢餓的發生,並且可以保證FIFO(先進先出)的服務順序.
CLH鎖的基本思想如下:鎖維護一個等待線程隊列,所有申請鎖,但是沒有成功的線程都記錄在這個隊列中,每一個節點代表一個線程,保存一個標記位(locked).用與判斷當前線程是否已經釋放鎖;locked=true 沒有獲取到鎖,false 已經成功釋放了鎖
當一個線程視圖獲得鎖時,取得等待隊列的尾部節點作為其前序節點.並使用類似如下代碼判斷前序節點是否已經成功釋放鎖:
while (pred.locked) {
}
只要前序節點(pred)沒有釋放鎖,則表示當前線程還不能繼續執行,因此會自旋等待,
反之,如果前序線程已經釋放鎖,則當前線程可以繼續執行.
釋放鎖時,也遵循這個邏輯,線程會將自身節點的locked位置標記位false,那么后續等待的線程就能繼續執行了
