簡介
ReentrantLock即可重入鎖(當前執行緒獲取該鎖再次獲取不會被阻塞),是一種遞迴無阻塞的同步機制。ReentrantLock基於AQS來實現,相對於內建鎖synchronized關鍵字功能更強大,多了等待可中斷、公平性、繫結多個條件等機制,還可以tryLock()避免死鎖,而若單獨從效能角度出發,更推薦synchronized
ReentrantLock
鎖獲取鎖流程:
lock方法:
public void lock() {
sync.lock();
}
複製程式碼
Sync為ReentrantLock裡面的一個內部類,它繼承AQS,它有兩個子類:公平鎖FairSync和非公平鎖NonfairSync,ReentrantLock裡面大部分的功能都是委託給Sync來實現的,以非公平鎖為例其lock()方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
複製程式碼
若鎖未執行緒佔有,把同步器中的exclusiveOwnerThread設定為當前執行緒
若鎖已有執行緒佔有,nonfairTryAcquire方法中,會再次嘗試獲取鎖,在這段時間如果該鎖被成功釋放,就可以直接獲取鎖而不用掛起,其完整流程:
公平鎖與非公平鎖
公平鎖與非公平鎖的區別在於獲取鎖的時候是否按照FIFO的順序。
ReentrantLock預設採用非公平鎖(組合方式)
public ReentrantLock() {
sync = new NonfairSync();
}
複製程式碼
實現非公平鎖的核心方法nonfairTryAcquire(),其原始碼如下:
final boolean nonfairTryAcquire(int acquires) {
//獲取當前執行緒
final Thread current = Thread.currentThread();
//獲取同步狀態
int c = getState();
// 若同步狀態為0,表明該鎖未被任何執行緒佔有
if (c == 0) {
// CAS設定同步狀態
if (compareAndSetState(0, acquires)) {
// 設定鎖的擁有執行緒
setExclusiveOwnerThread(current);
return true;
}
}
// 檢查佔有執行緒是否是當前執行緒,可重入性關鍵程式碼
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
複製程式碼
其主要邏輯:判斷同步狀態是否為0,若為0表明該鎖未被任何執行緒佔有,CAS設定同步狀態;若不為0表明該鎖已被執行緒佔有,判斷鎖佔有執行緒是否是當前執行緒,若是增加同步狀態(可重入性機制實現的關鍵)
公平鎖,通過ReentrantLock有參構造方法傳入true
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
複製程式碼
實現公平鎖的核心方法tryAcquire(),其原始碼如下:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
複製程式碼
可以很明顯地發現與nonfairTryAcquire()方法唯一的區別在於CAS設定嘗試設定state值之前,呼叫了hasQueuedPredecessors()判斷當前執行緒是否位於CLH同步佇列中的第一個,若不是先執行完同步佇列中結點的執行緒,當前執行緒進入等待狀態
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
複製程式碼
可重入性
可重入性需要解決以下兩個問題:
①.執行緒再次獲取鎖:鎖需要去識別獲取鎖的執行緒是否為當前佔據鎖的執行緒,如果是則再次成功獲取
次成功獲取
②.鎖的最終釋放:執行緒重複n次獲取了鎖,只有在n次釋放該鎖後,其他執行緒才能獲取到該鎖
在nonfairTryAcquire()、tryAcquire()方法中都有這段程式碼:
if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
複製程式碼
為了支援可重入性,若同步狀態不為0時,還會再判斷鎖持有執行緒是否是當前請求執行緒,若是再次獲取該鎖,同步狀態加1。再來看看釋放鎖:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 同步狀態為0時,鎖才能釋放,將其持有執行緒置為null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
複製程式碼
只有同步狀態完全釋放了,才能返回true。可以看到,該方法將同步狀態是否為0作為最終釋放的條件,當同步狀態為0時,將佔有執行緒設定為null,並返回true,表示釋放成功。
繫結多個條件
每一個Lock可以有任意資料的Condition物件,Condition是與Lock繫結的。Condition介面定義的方法,await對應於Object.wait,signal對應於Object.notify,signalAll對應於Object.notifyAll。
生產者消費者簡單demo:
public class Resource {
private int num = 1;//當前數量
private int maxNum = 10;//極值
private Lock lock = new ReentrantLock();
private Condition productCon = lock.newCondition();
private Condition consumerCon = lock.newCondition();
public void product() {
lock.lock();
try {
while (num >= maxNum) {
try {
System.out.println("當前已滿");
productCon.await();
} catch (InterruptedException e) {
}
}
num++;
System.out.println("生產者" + Thread.currentThread().getName() + "當前有" + num + "個");
consumerCon.signal();
} finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
try {
while (num == 0) {
try {
System.out.println("當前已空");
consumerCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println("消費者" + Thread.currentThread().getName() + "當前有" + num + "個");
productCon.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final Resource r = new Resource();
// 生產者
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
r.product();
}
}
}).start();
// 消費者
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
r.consume();
}
}
}).start();
}
}
複製程式碼
感謝
《java併發程式設計的藝術》
https://www.jianshu.com/p/4358b1466ec9