ReentrantLock即可重入鎖(當前線程獲取該鎖再次獲取不會被阻塞),是一種遞歸無阻塞的同步機制。ReentrantLock基於AQS來實現,相對於內置鎖synchronized關鍵字功能更強大,多了等待可中斷、公平性、綁定多個條件等機制,還能夠tryLock()避免死鎖,而若單獨從性能角度出發,更推薦synchronizedjava
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方法中,會再次嘗試獲取鎖,在這段時間若是該鎖被成功釋放,就能夠直接獲取鎖而不用掛起,其完整流程:ide
公平鎖與非公平鎖的區別在於獲取鎖的時候是否按照FIFO的順序。
性能
ReentrantLock默認採用非公平鎖(組合方式)ui
public ReentrantLock() {
sync = new NonfairSync();
}
複製代碼
實現非公平鎖的核心方法nonfairTryAcquire(),其源碼以下:spa
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有參構造方法傳入truecode
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
複製代碼
實現公平鎖的核心方法tryAcquire(),其源碼以下:cdn
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