上文簡(jiǎn)單描述了ReentrantLock中的方法大部分都是由AbstractQueuedSynchronizer或它的子類實(shí)現(xiàn),AQS隊(duì)列和條件隊(duì)列節(jié)點(diǎn)持有的信息,和它們運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)是什么樣子的。
本文結(jié)合最常用的lock和unlock方法來(lái)描述AQS隊(duì)列是怎么樣構(gòu)建和運(yùn)作的。先看一個(gè)簡(jiǎn)單的demo.
public class LockDemo extends Thread {
private Lock lock = new ReentrantLock();
public LockDemo(Lock lock){
this.lock = lock;
}
public void run() {
try {
this.lock.lock();
System.out.println("我獲取到鎖了:"+Thread.currentThread().getName());
} finally {
this.lock.unlock();
}
}
public static void main(String[] args) {
Lock lock = new ReentrantLock();
LockDemo lockDemo1 = new LockDemo(lock);
lockDemo1.setName("lock1");
LockDemo lockDemo2 = new LockDemo(lock);
lockDemo2.setName("lock2");
LockDemo lockDemo3 = new LockDemo(lock);
lockDemo3.setName("lock3");
lockDemo1.start();
lockDemo2.start();
lockDemo3.start();
}
}
輸出的結(jié)果如下,如我們所料,控制臺(tái)是串行輸出了結(jié)果:
我獲取到鎖了:lock1
我獲取到鎖了:lock2
我獲取到鎖了:lock3
先簡(jiǎn)單畫下lock()方法代碼流程圖。

ok,下面開始聊實(shí)現(xiàn)細(xì)節(jié)。ReentrantLock默認(rèn)是以非公平模式構(gòu)建,后面會(huì)簡(jiǎn)單說(shuō)下公平模式和非公平模式構(gòu)建的區(qū)別。廢話 不多說(shuō),開始NonfairSync.lock()。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
先用CAS嘗試設(shè)置鎖的狀態(tài),成功則把持有鎖的線程設(shè)置為當(dāng)前線程。否則執(zhí)行acquire(1),這里為什么是1,因?yàn)槭荝eentrantLock是可重入鎖,同一個(gè)線程每次獲取鎖都會(huì)+1。接著看aqs.accquire;
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire是由AQS的子類,這里也就是NonfairSync去實(shí)現(xiàn)tryAcquire。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
第一步判斷當(dāng)前鎖是否被持有,如果沒(méi)有被持有則cas設(shè)置鎖的狀態(tài),更新持有鎖的線程。如果鎖已經(jīng)被持有,則進(jìn)行第二步判斷,如果持有鎖的線程是當(dāng)前線程,則進(jìn)行重入鎖的計(jì)算。上面兩步都沒(méi)有成功,則該線程持有鎖的動(dòng)作失敗。開始構(gòu)建AQS隊(duì)列??聪耡cquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,先看addWaiter(Node.EXCLUSIVE)。
private Node addWaiter(Node mode) {
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;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
先構(gòu)建當(dāng)前線程的節(jié)點(diǎn)。模式是獨(dú)占鎖,獨(dú)占鎖的意思就是這個(gè)鎖只能被當(dāng)前線程一個(gè)線程去持有。然后判斷AQS隊(duì)列尾節(jié)點(diǎn),是不是為空,如果不為空則直接把當(dāng)前節(jié)點(diǎn)CAS操作為尾節(jié)點(diǎn)。否則執(zhí)行enq(node)方法
private Node enq(final Node node) {
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;
}
}
}
}
這是一個(gè)for循環(huán),退出去的條件就是當(dāng)前節(jié)點(diǎn)置位尾節(jié)點(diǎn)成功。先判斷當(dāng)前AQS隊(duì)列是否存在尾節(jié)點(diǎn)。不存在說(shuō)明隊(duì)列不存在。則CAS操作生成一個(gè)頭節(jié)點(diǎn),并且置位尾節(jié)點(diǎn)。注意該節(jié)點(diǎn)并不持有任何線程信息,就是一個(gè)空節(jié)點(diǎn)。如果尾節(jié)點(diǎn)不為空。則把當(dāng)前節(jié)點(diǎn)置位尾節(jié)點(diǎn),之前的尾節(jié)點(diǎn)的next置位當(dāng)前節(jié)點(diǎn)。從而構(gòu)造了一個(gè)雙向鏈表。構(gòu)造節(jié)點(diǎn)成功后,開始執(zhí)行acquireQueued方法。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
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);
}
}
這里也是一個(gè)for循環(huán),退出循環(huán)的條件為把當(dāng)前節(jié)點(diǎn)置位頭結(jié)點(diǎn)(頭結(jié)點(diǎn)不包含持有線程),首先獲取當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn),如果當(dāng)前節(jié)點(diǎn)是AQS隊(duì)列第一次構(gòu)建出來(lái)的,那么它的前置節(jié)點(diǎn)顯然就是頭節(jié)點(diǎn)。此時(shí)會(huì)去嘗試持有鎖,如果持有鎖成功了,把當(dāng)前 節(jié)點(diǎn)置位頭結(jié)點(diǎn)。并把之前的頭結(jié)點(diǎn)的next置為null,減少?gòu)?qiáng)引用方便GC。
如果當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)或者持有鎖的動(dòng)作失敗了。則執(zhí)行shouldParkAfterFailedAcquire(p, node)。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
第一次進(jìn)來(lái)的時(shí)候,waitStatus初始值是0,會(huì)直接走到compareAndSetWaitStatus(pred, ws, Node.SIGNAL);會(huì)把當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)的狀態(tài)設(shè)置為SIGNAL,意思需要喚醒下一個(gè)節(jié)點(diǎn)。返回false。
也就是說(shuō)會(huì)進(jìn)入第二次循環(huán),過(guò)程同上。也就是說(shuō)會(huì)第二次進(jìn)入shouldParkAfterFailedAcquire,如果waitStatus沒(méi)有被修改過(guò)則返回true。如果被修改過(guò),有可能被取消ws > 0,因?yàn)橹挥腥∠臓顟B(tài)是大于0的,這個(gè)時(shí)候需要一直往當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)找,一直找到不是取消狀態(tài)的節(jié)點(diǎn)。這里為什么不給waitStatus設(shè)置默認(rèn)為SIGNAL呢,減少循環(huán)次數(shù)。博客園有個(gè)大神說(shuō)過(guò),這是jdk大神故意為之,這樣做盡量多執(zhí)行一次獲取鎖的動(dòng)作,類似自旋。因?yàn)槿绻@里判斷為true后,該線程多半會(huì)阻塞 ,阻塞是會(huì)引起線程上下文切換降低吞吐量,很有道理。接著看:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
LockSupport.park是jdk提供的unsafe方法,大意就是阻塞該線程。并返回線程的中斷狀態(tài)。當(dāng)然前置節(jié)點(diǎn)的線程沒(méi)有unlock之前是不會(huì)返回的啦。lock方法的閱讀就到此為止了。下面接著看unlock代碼。圖就不畫了,直接開始。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
這邊意思比較簡(jiǎn)單,如果當(dāng)前線程釋放鎖成功,則喚醒隊(duì)列頭節(jié)點(diǎn)的后置節(jié)點(diǎn),先看下tryRelease。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
releases值是1,狀態(tài)值減1為0的話說(shuō)明鎖已經(jīng)被 完全釋放,持有鎖的線程設(shè)置為null。如果不為0說(shuō)明鎖被重入過(guò),這也說(shuō)明了。調(diào)用了多少次lock,就必須有多少次unlock對(duì)應(yīng),不然就沒(méi)有完全釋放鎖。然后看下unparkSuccessor。
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
先把頭節(jié)點(diǎn)waitStatus設(shè)置為默認(rèn)值。獲取頭結(jié)點(diǎn)的后置節(jié)點(diǎn),如果后置節(jié)點(diǎn)為null或者狀態(tài)為取消,然后從鏈表的尾部開始遍歷,直到找到鏈表中第一個(gè)不是取消狀態(tài)的節(jié)點(diǎn)。這里為什么不直接從頭結(jié)點(diǎn)往下遍歷呢?最后再取消阻塞該節(jié)點(diǎn)。這個(gè)節(jié)點(diǎn)又開始執(zhí)行acquireQueued的邏輯啦。
最后說(shuō)一下公平鎖和非公平鎖中的區(qū)別??聪鹿芥i的邏輯。
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;
}
相對(duì)非公平鎖多了hasQueuedPredecessors的判斷,這個(gè)方法的大概意思就是隊(duì)列中是否存在等待的節(jié)點(diǎn)。如果有的話,公平鎖的邏輯就不能直接嘗試持有鎖,而是需要進(jìn)入到隊(duì)列中排隊(duì)。
ReentrantLock的關(guān)鍵方法已經(jīng)分析完了,雖然是鎖,但是jdk大神的設(shè)計(jì)中無(wú)處不提現(xiàn)出樂(lè)觀的思想,減少因?yàn)榫€程阻塞引起的上下文切換,這也是為什么之前說(shuō)ReentrantLock比synchronized的性能要好的多,當(dāng)然synchronized經(jīng)過(guò)各種優(yōu)化已經(jīng)很好很好啦,同樣是用了樂(lè)觀鎖來(lái)替代悲觀的操作。