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異常。
原理支援
- 依賴CAS方法,可重入實現用的計數就是用的原子變數。
- 依賴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為不公平鎖。以不公平鎖為例先講一下上鎖和釋放鎖的原理:
上鎖
- 如果當前鎖狀態為0(未被鎖),則使用CAS獲得鎖,並設定當前鎖內的執行緒為自己。
- 如果不為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方法時,子執行緒才被