1. 程式人生 > >可重入鎖和非可重入鎖

可重入鎖和非可重入鎖

    廣義上的可重入鎖指的是可重複可遞迴呼叫的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個物件或者class),這樣的鎖就叫做可重入鎖。

可重入鎖:

    ReentrantLock和synchronized都是可重入鎖,下面是一個用synchronized實現的例子:

public class ReentrantTest implements Runnable {

    public synchronized void get() {
        System.out.println(Thread.currentThread().getName());
        set();
    }

    public synchronized void set() {
        System.out.println(Thread.currentThread().getName());
    }

    public void run() {
        get();
    }

    public static void main(String[] args) {
        ReentrantTest rt = new ReentrantTest();
        for(;;){
            new Thread(rt).start();
        }
    }
}

    整個過程沒有發生死鎖的情況,擷取一部分輸出結果如下: 

Thread-8492
Thread-8492
Thread-8494
Thread-8494
Thread-8495
Thread-8495
Thread-8493
Thread-8493

    set()和get()同時輸出了執行緒名稱,表明即使遞迴使用synchronized也沒有發生死鎖,證明其是可重入的。

 

不可重入鎖

    不可重入鎖,與可重入鎖相反,不可遞迴呼叫,遞迴呼叫就發生死鎖。

自定義模擬不可重入鎖:

    一個經典的講解,使用自旋鎖來模擬一個不可重入鎖,程式碼如下:

package com.thread;

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {
    private AtomicReference<Thread> owner = new AtomicReference<Thread>();//記錄當前鎖的持有執行緒物件

    public void lock() {//加鎖
        Thread current = Thread.currentThread();//獲取當前執行緒物件
        for (; ; ) {//自旋(被當前執行緒或其他執行緒持有鎖,就會迴圈)
            if (owner.compareAndSet(null, current)) {//只有鎖可用即為null,才能設定當前執行緒為鎖持有物件,並返回true
                return;
            }
        }
    }

    public void unlock() {//解鎖
        Thread current = Thread.currentThread();//獲取當前執行緒物件
        owner.compareAndSet(current, null);//設定鎖的持有物件為null
    }
}

    使用原子引用來存放執行緒,同一執行緒兩次呼叫lock()方法,如果不執行unlock()釋放鎖的話,第二次呼叫自旋的時候就會產生死鎖,這個鎖就不是可重入的。

自定義模擬可重入鎖:

    實際上同一個執行緒不必每次都去釋放鎖再來獲取鎖,這樣的排程切換是很耗資源的。稍微改一下,把它變成一個可重入鎖:

package com.thread;

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {
    private AtomicReference<Thread> owner = new AtomicReference<Thread>();//記錄當前鎖的持有執行緒物件
    private int state = 0;//記錄重入次數

    public void lock() {//加鎖
        Thread current = Thread.currentThread();//獲取當前執行緒物件
        if (owner.compareAndSet(null, current)) {//當前鎖可用
            state = 1;//狀態置為1
            return;
        } else {
            if (current == owner.get()) {//如果當前執行緒持有鎖
                state++;//重入次數加1
                return;
            }
            for (; ; ) {//被其他執行緒持有就會繼續迴圈
                if (owner.compareAndSet(null, current)) {//只有鎖可用即為null,才能設定當前執行緒為鎖持有物件,並返回true
                    return;
                }
            }
        }
    }

    public void unlock() {//解鎖
        Thread current = Thread.currentThread();//獲取當前執行緒物件
        if (current == owner.get()) {//如果當前執行緒持有鎖
            if (state > 0) {//重入次數大於0
                state--;//重入次數減1
            } else {
                owner.compareAndSet(current, null);//設定鎖的持有物件為null
            }
        }
    }
}

    在執行每次操作之前,判斷當前鎖持有者是否是當前物件,採用state計數,不用每次去釋放鎖。

ReentrantLock中非公平的可重入鎖實現:

// 非公平方式獲取鎖,用於tryLock()方法
final boolean nonfairTryAcquire(int acquires) {
   //當前執行緒
   final Thread current = Thread.currentThread();
   // 繼承至AbstractQueuedSynchronizer的方法
   int c = getState();//獲取鎖狀態值

   //沒有執行緒正在競爭該鎖
   if (c == 0) {
      // 繼承至AbstractQueuedSynchronizer的方法
      if (compareAndSetState(0, acquires)) {//若state為0則將state修改為acquires的值,狀態0表示鎖沒有被佔用
         setExclusiveOwnerThread(current);// 設定當前執行緒獨佔
         return true;// 成功
      }
   } else if (current == getExclusiveOwnerThread()) {// 當前執行緒擁有該鎖
      int nextc = c + acquires;// 增加重入次數
      if (nextc < 0) // overflow(計數值小於0,則丟擲異常)
         throw new Error("Maximum lock count exceeded");
      // 繼承至AbstractQueuedSynchronizer的方法
      setState(nextc);//設定鎖狀態值
      return true;// 成功
   }
   return false;// 失敗
}