面試官:知道Java1.8中新加的StampedLock嗎?

yes的練級手冊發表於2019-04-20

Java1.8引入了一個新鎖StampedLock,這個鎖可以認為是ReadWriteLock的改進。

我們知道在ReadWriteLock中寫和讀是互斥的,也就是如果有一個執行緒在寫共享變數的話,其他執行緒讀共享變數都會阻塞。

StampedLock把讀分為了悲觀讀和樂觀讀,悲觀讀就等價於ReadWriteLock的讀,而樂觀讀在一個執行緒寫共享變數時,不會被阻塞,樂觀讀是不加鎖的。所以沒鎖肯定是比有鎖的效能好,這樣的話在大併發讀情況下效率就更高了!

StampedLock的用法稍稍有點不同,在獲取鎖和樂觀讀時,都會返回一個stamp,解鎖時需要傳入這個stamp,在樂觀讀時是用來驗證共享變數是否被其他執行緒寫過。來看一下官方示例

 class Point {
   private double x, y;
   private final StampedLock sl = new StampedLock();

   void move(double deltaX, double deltaY) { // an exclusively locked method
     long stamp = sl.writeLock();  //獲取寫鎖
     try {
       x += deltaX;
       y += deltaY;
     } finally {
       sl.unlockWrite(stamp); //釋放寫鎖
     }
   }

   double distanceFromOrigin() { // A read-only method
     long stamp = sl.tryOptimisticRead(); //樂觀讀
     double currentX = x, currentY = y;
     if (!sl.validate(stamp)) { //判斷共享變數是否已經被其他執行緒寫過
        stamp = sl.readLock();  //如果被寫過則升級為悲觀讀鎖
        try {
          currentX = x;
          currentY = y;
        } finally {
           sl.unlockRead(stamp); //釋放悲觀讀鎖
        }
     }
     return Math.sqrt(currentX * currentX + currentY * currentY);
   }

   void moveIfAtOrigin(double newX, double newY) { // upgrade
     // Could instead start with optimistic, not read mode
     long stamp = sl.readLock(); //獲取讀鎖
     try {
       while (x == 0.0 && y == 0.0) {
         long ws = sl.tryConvertToWriteLock(stamp);  //升級為寫鎖
         if (ws != 0L) {
           stamp = ws;
           x = newX;
           y = newY;
           break;
         }
         else {
           sl.unlockRead(stamp);
           stamp = sl.writeLock();
         }
       }
     } finally {
       sl.unlock(stamp);
     }
   }
 }
複製程式碼

其上的操作在樂觀讀時,如果有寫操作修改了共享變數則升級樂觀讀為悲觀讀鎖,這樣避免樂觀讀反覆的迴圈等待寫鎖的釋放,避免浪費CPU資源。所以在我們的使用StampedLock的時候,建議這樣操作。

看起來好像StampedLock效能又比ReadWriteLock鎖好,那是不是都可以用StampedLock拋棄ReadWriteLock?

並不是的,StampedLock不是可重入鎖,所以不支援重入,並且StampedLock不支援條件變數,也就是沒Condition。如果是執行緒使用writeLock()或者readLock()獲得鎖之後,執行緒還沒執行完就被interrupt()的話,會導致CPU飆升....坑啊 我們來看下原始碼

public long readLock() {
    long s = state, next;  // bypass acquireRead on common uncontended case
    return ((whead == wtail && (s & ABITS) < RFULL &&
            U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
            next : acquireRead(false, 0L));   //當CAS失敗之後就會嘗試申請鎖,注意第一個引數是false
}

public long writeLock() {
    long s, next;  // bypass acquireWrite in fully unlocked case only
    return ((((s = state) & ABITS) == 0L &&
            U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
            next : acquireWrite(false, 0L)); //當CAS失敗之後就會嘗試申請鎖,注意第一個引數是false
}
//就拿acquireWrite舉例,acquireRead也是類似的。
private long acquireWrite(boolean interruptible, long deadline) {
        WNode node = null, p;
        for (int spins = -1;;) { // spin while enqueuing
             //省略程式碼無數
            if (interruptible && Thread.interrupted())
                return cancelWaiter(node, node, true);
                }
    }

複製程式碼

首先裡面是個無限迴圈,然後 if (interruptible && Thread.interrupted())已經得知呼叫的interruptible引數傳入的是false,所以Thread.interrupted()也不會執行到,也一定呼叫不到cancelWaiter,所以就一直迴圈迴圈,CPU使用率就會漲漲漲。

所以如果要使用中斷功能就得用readLockInterruptibly()或者writeLockInterruptibly()來獲得鎖。


如有錯誤歡迎指正! 個人公眾號:yes的練級攻略

相關文章