class
- abstract static class Sync extends AbstractQueuedSynchronizer
- static final class NonfairSync extends Sync
- static final class FairSync extends Sync
程式碼使用示例
private ReentrantLock reentrantLock = new ReentrantLock();
public void test (){
reentrantLock.lock();
System.out.println(Thread.currentThread().getName()+ "test....begin");
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"test....end");
reentrantLock.unlock();
}
複製程式碼
ReentrantLock 提供兩種方式加鎖 公平和非公平 預設為非公平 如果要使用非公平建構函式傳true 加公平鎖呼叫FairSync.lock() 加非公平鎖呼叫NonfairSync.lock() 釋放鎖呼叫AbstractQueuedSynchronizer.release(int arg)
程式碼解析
NonfairSync.lock 程式碼
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
複製程式碼
- 先呼叫AbstractQueuedSynchronizer.compareAndSetState(int expect, int update) AQS通過CAS維護了state欄位,0代表沒有被執行緒佔有 所以非公平鎖先嚐試通過CAS替換state,如果替換成功代表沒有被別的執行緒佔有, 設定當前執行緒佔有,lock方法結束,執行緒可以繼續往下走了
- 如果沒有替換成功再呼叫AbstractQueuedSynchronizer.acquire(int arg)
AbstractQueuedSynchronizer.acquire(int arg)程式碼
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
複製程式碼
- tryAcquire(arg) 嘗試獲取鎖 NonfairSync.tryAcquire(int acquires)--> Sync.nonfairTryAcquire(int acquires) 程式碼片段
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); 獲取state欄位的值
if (c == 0) { 如果值是0 代表沒有執行緒佔用
if (compareAndSetState(0, acquires)) { 通過CAS替換看自己是否可以佔用成功
setExclusiveOwnerThread(current);
return true; 如果成功就返回true
}
}
如果有執行緒佔用,判斷佔用的執行緒和自己是否是同一個執行緒
else if (current == getExclusiveOwnerThread()) {
//如果是同一個執行緒 state欄位加上傳入的引數
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//更新state
setState(nextc);
//從這可以看出 NonfairSync.lock是重入鎖
return true;
}
//如果都沒滿足,代表加鎖不成功
return false;
}
複製程式碼
- 如果tryAcquire(arg)返回ture,代表加鎖成功,後面就不用走了 如果加鎖不成功,繼續acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
- 先看addWaiter(Node.EXCLUSIVE) 呼叫的是AbstractQueuedSynchronizer.addWaiter AbstractQueuedSynchronizer.addWaiter程式碼片段
private Node addWaiter(Node mode) {
//把自己當前執行緒封裝成一個node
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//判斷隊尾是不是空
if (pred != null) {
//如果隊尾不是空
node.prev = pred;
//就把自己node的上一個結點設定為隊尾那個結點
更新自己結點為隊尾結點
if (compareAndSetTail(pred, node)) {
//如果更新成功,把上一個結點的下一個結點設定為自己結點
pred.next = node;
//返回當前結點
return node;
}
}
enq(node);
return node;
}
複製程式碼
如果隊尾為空或者更新自己為隊尾結點失敗繼續enq(node) AbstractQueuedSynchronizer.enq片段
private Node enq(final Node node) {
無限迴圈CAS操作,直到把自己變成隊尾結點
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
複製程式碼
總結:addWaiter(Node.EXCLUSIVE) 是組裝當前執行緒結點,然後通過CAS新增到隊尾
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 通過addWaiter(Node.EXCLUSIVE)已經把自己加入佇列了 那acquireQueued這個方法是做什麼呢
AbstractQueuedSynchronizer.acquireQueued程式碼片段
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//獲得當前結點的前一個結點
final Node p = node.predecessor();
//如果head是前一個結點,我就再次嘗試獲取鎖,萬一獲取成功了呢
if (p == head && tryAcquire(arg)) {
//如果加鎖成功就把自己設定為頭結點
setHead(node);
p.next = null; // help GC
failed = false;
//獲取成功 就代表獲得鎖
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
複製程式碼
如果還沒有獲得的鎖就走shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt() 先看shouldParkAfterFailedAcquire(p, node) AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire程式碼片段
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//入參是前一個結點和當前結點
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//如果前一個是-1,代表可以安心的等待
return true;
if (ws > 0) {
//如果前一個大於0,代表前一個等待超時或者被中斷了,需要從同步佇列中取消該Node的結點,所以繼續找在等待的前結點,把找到的結點的下一個結點設為當前結點
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果是小於0的其他狀態都統一設為-1,等待狀態
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
複製程式碼
這個方法其實就是如果前驅結點的狀態不是SIGNAL,那麼自己就不能安心去休息,需要去找個安心的休息點,然後自己設為這個前驅結點的後一個結點
附上node結點的waitStatus含義:
- CANCELLED:值為1,在同步佇列中等待的執行緒等待超時或被中斷,需要從同步佇列中取消該Node的結點,其結點的waitStatus為CANCELLED,即結束狀態,進入該狀態後的結點將不會再變化。
- SIGNAL:值為-1,被標識為該等待喚醒狀態的後繼結點,當其前繼結點的執行緒釋放了同步鎖或被取消,將會通知該後繼結點的執行緒執行。說白了,就是處於喚醒狀態,只要前繼結點釋放鎖,就會通知標識為SIGNAL狀態的後繼結點的執行緒執行。
- CONDITION:值為-2,與Condition相關,該標識的結點處於等待佇列中,結點的執行緒等待在Condition上,當其他執行緒呼叫了Condition的signal()方法後,CONDITION狀態的結點將從等待佇列轉移到同步佇列中,等待獲取同步鎖。
- PROPAGATE:值為-3,與共享模式相關,在共享模式中,該狀態標識結點的執行緒處於可執行狀態。
- 0狀態:值為0,代表初始狀態
如果找到安全休息點後就繼續parkAndCheckInterrupt方法 真正讓執行緒等待的方法 AbstractQueuedSynchronizer.parkAndCheckInterrupt程式碼片段
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//呼叫park()使執行緒進入waiting狀態
return Thread.interrupted();//如果被喚醒,檢視自己是不是被中斷的。
}
複製程式碼
park()會讓當前執行緒進入waiting狀態。在此狀態下,有兩種途徑可以喚醒該執行緒:1)被unpark();2)被interrupt()。需要注意的是,Thread.interrupted()會清除當前執行緒的中斷標記位
前面已經介紹了NonfairSync.lock(),再看下FairSync.lock() FairSync.lock()程式碼片段
final void lock() {
acquire(1);
}
複製程式碼
相比於NonfairSync.lock(),少了一個CAS判斷,因為是公平鎖,所以不像非公平鎖那樣直接上來你嘗試自己能不能獲得鎖 後面呼叫邏輯只是在FairSync.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;
}
}
複製程式碼
比較Sync.nonfairTryAcquire(int acquires)只是多了!hasQueuedPredecessors()這個判斷 就是在嘗試獲取鎖的時候,不是直接去獲取,而是看佇列中有沒有其他等待的執行緒,如果有,自己是不能直接去獲取鎖的
小結:ReentrantLock的公平鎖和非公平鎖加鎖就講完了,公平和非公平體現在 公平獲取鎖時是有先來後到的,非公平在嘗試獲取鎖時,是不管佇列中是否有其他執行緒在等待,自己直接去CAS嘗試加鎖,如果不成功才放入佇列
ReentrantLock.unlock()其實呼叫的是AbstractQueuedSynchronizer.release(1) AbstractQueuedSynchronizer.release程式碼片段
public final boolean release(int arg) {
if (tryRelease(arg)) {//釋放可以釋放鎖資源
Node h = head; //找到佇列頭
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//根據頭結點喚醒下一個執行緒
return true;
}
return false;
}
複製程式碼
tryRelease呼叫的是ReentrantLock.tryRelease 程式碼片段
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//計算state狀態量
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//如果是0代表可以釋放
free = true;
setExclusiveOwnerThread(null);//清空鎖資源佔有的執行緒
}
setState(c);設定變數
return free;
}
複製程式碼
這個是重入鎖釋放的設計,因為在獲取鎖時,同一個執行緒可以多次對同一個資源加鎖的,每加一次的時候state都會加1,釋放也一樣都會減一,只有當state等於0的時候才代表這個執行緒釋放完了,所以在寫ReentrantLock時,lock和unlock要成對出現,否則會一直在那佔有資源
接下來看AbstractQueuedSynchronizer.unparkSuccessor程式碼片段
private void unparkSuccessor(Node node) {
//這裡,node一般為當前執行緒所在的結點
int ws = node.waitStatus;
if (ws < 0) //置零當前執行緒所在的結點狀態,允許失敗。
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next; //找到下一個需要喚醒的結點s
if (s == null || s.waitStatus > 0) {//如果為空或已取消
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0) //從這裡可以看出,<=0的結點,都是還有效的結點
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); //喚醒
}
複製程式碼
最終就是找到下一個有效的結點,然後喚醒該結點對應的執行緒