六. Java 鎖分類
《Java并發程式設計:Lock》
《java 鎖 Lock接口詳解》
《[死磕 java同步系列之ReentrantLock源碼解析(一)——公平鎖、非公平鎖]》)
6.1 Java 鎖的分類
鎖的類型目前感覺可以分成兩大類:synchronized 關鍵字,以及 Lock, ReadWriteLock 鎖以及 Reentrant 為字首修飾的實作類 (ReentrantLock, ReentrantReadWriteLock);
其他角度來看,按照不同分類類型的鎖:
- 實作方式:synchronized / Lock, ReadWriteLock 及其實作類;
- 可中斷性:synchronized / Lock
- 公平性:ReentrantLock 構造函數中,傳入 boolean 值,可以控制公平性,預設非公平;公平鎖按照鎖的申請順序配置設定鎖,申請鎖時間最長的線程在下一次會最早得到鎖;非公平鎖不對申請鎖的時間進行保證,是以可能導緻某個線程永遠都擷取不到鎖;
- 可重入性:synchronized / ReentrantLock,如果一個線程已經獲得了一個對象鎖,此後該線程再請求進入被該對象鎖的同步代碼塊時,由于該線程之前已經擷取了這個對象鎖,是以可以直接進入該鎖的同步代碼塊;
- 讀寫性:ReadWriteLock;
可重入性的原理:參考《深入了解 Java 虛拟機》P391
可重入鎖和不可重入:參考《Java不可重入鎖和可重入鎖了解》
對于一個對象,進入對象鎖的代碼域之後,線程對該鎖進行計數。沒有進入鎖時,該鎖的計數值為 0。第一個擷取到該鎖的線程獲得該對象鎖,此後每多一個線程對該鎖進行申請,則計數值 +1。每當有一個線程釋放了這個鎖,則該鎖對應的計數值 -1。直到這個鎖的計數值重新回到 0,其他線程才可以擷取到該鎖的所有權。
我對于對象鎖的了解:
- 對于 synchronized,可以為 synchronized 方法的執行個體對象 this,或者 synchronized(object) 的 object,都是對象鎖;
- 對于 Lock,就是 lock 對象本身;
不可重入鎖:以可重入的對立面了解即可:對于某個 Lock 的實作,如果該 Lock 被線程A鎖住,線程A的其他對象想要擷取該 Lock,但由于在此之前 Lock 已經被鎖住,是以這裡不能擷取到該 Lock。總之感覺不可重入鎖并不是一種合适的 Lock 的類型。
不可重入鎖的代碼執行個體如下:
public class Lock {
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException {
while(isLocked) {
wait();
}
isLocked = true;
}
public synchronized void unlock() {
isLocked = false;
notify();
}
}
public class Count {
Lock lock = new Lock();
public void print() {
lock.lock();
doAdd();
lock.unlock();
}
public void doAdd() {
lock.lock();
//do something
lock.unlock();
}
}
複制
Lock 接口主要有六個方法:
// 擷取鎖
void lock();
// 擷取鎖,可中斷
void lockInterruptibly() throws InterruptedException;
// 嘗試擷取鎖;如果沒有擷取到,則傳回 false;如果擷取到則傳回 true
boolean tryLock();
// 嘗試在某段時間内擷取鎖,如果等待一段時間仍沒有擷取到則傳回 false;
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 釋放鎖
void unlock();
// 條件鎖
Condition newCondition();
複制
6.2 公平鎖與非公平鎖
為什麼ReentrantLock預設采用的是非公平模式?因為非公平模式效率比較高。非公平模式會在一開始就嘗試兩次擷取鎖,如果當時正好 state 的值為 0,它就會成功擷取到鎖,少了排隊導緻的阻塞/喚醒過程,并且減少了線程頻繁的切換帶來的性能損耗。
同時非公平模式也存在弊端。非公平模式有可能會導緻一開始排隊的線程一直擷取不到鎖,導緻線程餓死。
公平鎖與非公平鎖在源碼上的差別:
- 公平鎖:在嘗試擷取鎖的時候,首先會判斷 AQS 線程隊列的頭部是否為目前線程(隊列的特性決定了公平性),如果目前線程位于 AQS 線程隊列的頭部,則說明目前線程等待的時間最長,目前線程有資格比較狀态,然後才能擷取到鎖。
- 非公平鎖:嘗試擷取鎖的時候不會判斷 AQS 隊列頭部資訊,直接進行比較狀态并嘗試擷取鎖。