1. 程式人生 > >JDK併發包溫故知新系列(五)—— 顯式鎖與顯式條件

JDK併發包溫故知新系列(五)—— 顯式鎖與顯式條件

ReentrantReadWriteLock

兩把鎖共享一個等待佇列,兩把鎖的狀態都由一個原子變量表示,特有的獲取鎖和釋放鎖邏輯。

ReentrantReadWriteLock的基本原理:

  • 讀鎖的獲取,只要求寫鎖沒有被執行緒持有就可以獲取,檢查等待佇列,逐個喚醒等待讀鎖執行緒,遇到等待寫鎖執行緒則停止.
  • 讀鎖的釋放,釋放後,檢查寫鎖和讀鎖是否被持有,若都沒有被持有則喚醒下一個等待執行緒.
  • 寫鎖的獲取,只有讀寫鎖都未被持有才會獲取寫鎖。
  • 寫鎖的釋放,喚醒等待佇列的下一個執行緒。

ReentrantLock

主要方法

  • void lock();獲取鎖,阻塞,不響應中斷,但會記錄中斷標誌位。
  • void lockInterruptibly() throws InterruptedException;獲取鎖,響應中斷
  • boolean tryLock();獲取鎖,不阻塞,實時返回,一般需迴圈呼叫
  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException;在time的時間內阻塞獲取鎖,響應中斷
  • void unlock();釋放鎖
  • Condition newCondition();新建顯式條件

注: 這裡的響應中斷意思是若被其他執行緒中斷(呼叫interrupt方法)會丟擲InterruptedException異常。

原理支援

  1. 依賴CAS方法,可重入實現用的計數就是用的原子變數。
  2. 依賴LockSupport中的方法:
  • public static void park():放棄CPU執行權,CPU不在進行排程,響應中斷,當有中斷髮生時,park會返回,執行緒中斷狀態會被設定,另外park也有可能無緣無故的返回,所以一般需要迴圈檢查park的等待條件是否滿足。。
  • public static void parkNanos(long nanos):在nanos納秒內放棄CPU執行權
  • public static void parkUntil(long deadline):放棄執行權直到deadline時間(距離1970年毫秒數)。
  • public static void unpark(Thread thread):重新恢復執行緒,讓其爭奪CPU執行權。

實現基礎AQS

AQS-AbstractQueuedSynchronizer(抽象佇列同步器)。

ReadWriteLock在內部注入了AbstractQueuedSynchronizer,上鎖和釋放鎖核心方法都在AQS類當中,AQS維護了兩個核心變數,一個是state(當前可重入計數,初始值為0),一個是exclusiveOwnerThread(當前持有鎖的執行緒Thread物件)。另外還維護了一個鎖等待佇列。

ReentrantLock構造方法傳入的boolean值ture為公平鎖,false為不公平鎖。以不公平鎖為例先講一下上鎖和釋放鎖的原理:

上鎖

  1. 如果當前鎖狀態為0(未被鎖),則使用CAS獲得鎖,並設定當前鎖內的執行緒為自己。
  2. 如果不為0,新增到佇列尾部,並呼叫LockSupport中的park()方法放棄CPU執行權。直到當鎖被釋放的時候被喚醒,被喚醒後檢查自己是否是第一個等待的執行緒,如果是且能獲得鎖,則返回,否則繼續等待,這個過程中如果發生了中斷,lock會記錄中斷標誌位,但不會提前返回或丟擲異常。

釋放鎖

就是將AQS內的state變數的值遞減1,如果state值為0,則徹底釋放鎖,會將“加鎖執行緒”變數也設定為null,同時喚醒等待佇列中的所有執行緒。

公平鎖

在獲取鎖方法中多了一個檢查,意義是隻有不存在其他等待時間更長的執行緒,它才會嘗試獲取鎖。對比不公平鎖,其整體效能比較低,低的原因不是這個檢查慢,而是會讓活躍執行緒得不到鎖,進入等待狀態,引起上下文切換,降低了整體的效率,

與synchrnized的區別

  • tryLock可避免死鎖造成的無限等待
  • 擁有獲取鎖資訊方法的各種API
  • 可以響應中斷
  • 可以限時

建議: synchronized以前的效率不如顯式鎖,但現在的版本兩者效率上幾乎沒有區別,所以建議能用synchronized就用synchronized,需要實現synchronized辦不到的需求如以上區別時,再考慮ReentrantLock。

顯示條件

什麼是顯示條件

與wait和notify對應,用於執行緒協作,通過Lock的Condition newCondition()方法建立對應顯示鎖的顯示條件;

方法

主要方法是await()和signal(),await()對應於Object的wait(),signal()對應於notify,signalAll()對應於notifyAll()

用法示例

public class WaitThread extends Thread {
    private volatile boolean fire = false;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    @Override
    public void run() {
        try {
            lock.lock();
            try {
                while (!fire) {
                    condition.await();
                }
            } finally {
                lock.unlock();
            }
            System.out.println("fired");
        } catch (InterruptedException e) {
            Thread.interrupted();
        }
    }

    public void fire() {
        lock.lock();
        try {
            this.fire = true;
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WaitThread waitThread = new WaitThread();
        waitThread.start();
        Thread.sleep(1000);
        System.out.println("fire");
        waitThread.fire();
    }
}

當主執行緒呼叫fire方法時,子執行緒才被