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

顯式鎖-Lock與ReadWriteLock

JDK針對Lock的主要實現是ReentrantLock,ReadWriteLock實現是ReentrantReadWriteLock。本文主要介紹ReentrantLock。ide

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異常。this

原理支持

  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對象)。另外還維護了一個鎖等待隊列。code

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

上鎖

  1. 若是當前鎖狀態爲0(未被鎖),則使用CAS得到鎖,並設置當前鎖內的線程爲本身。
  2. 若是不爲0,且持有鎖的線程不是本身,則添加到隊列尾部,並調用LockSupport中的park()方法放棄CPU執行權。直到當鎖被釋放的時候被喚醒,被喚醒後檢查本身是不是第一個等待的線程,若是是且能得到鎖,則返回,不然繼續等待,這個過程當中若是發生了中斷,lock會記錄中斷標誌位,但不會提早返回或拋出異常。
  3. 若是不爲0,但持有鎖線程是本身,則直接將state加1。

釋放鎖

就是將AQS內的state變量的值遞減1,若是state值爲0,則完全釋放鎖,會將「加鎖線程」變量也設置爲null,同時喚醒等待隊列中的第一個線程。隊列

公平鎖

爲何說上面的是不公平鎖,釋放鎖時不是喚醒隊列中第一個線程嗎?爲何還會出現不公平的狀況了,緣由在於若是恰好釋放鎖,此時有一個線程進來嘗試獲取鎖,可能會存在插隊的狀況。同步

公平鎖原理

構造方法bollean傳入true則表明的是公平鎖,在獲取鎖方法中多了一個檢查,意義是隻有不存在其餘等待時間更長的線程,它纔會嘗試獲取鎖。對比不公平鎖,其總體性能比較低,低的緣由不是這個檢查慢,而是會讓活躍線程得不到鎖,進入等待狀態,引發上下文切換,下降了總體的效率,it

與synchrnized的區別

  • tryLock可避免死鎖形成的無限等待
  • 擁有獲取鎖信息方法的各類API
  • 能夠響應中斷
  • 能夠限時

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

顯示條件

什麼是顯示條件

與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方法時,子線程才被喚醒繼續執行。

相關文章
相關標籤/搜索