上一篇分析了關於AQS獨佔鎖的執行流程和源碼。在AQS中不僅涉及了獨佔鎖,還涉及了共享鎖及帶超時時間的共享鎖、中斷共享鎖。本文就講解上述鎖的獲取鎖和釋放鎖的原理。
AQS獲取共享鎖acquireShared
共享鎖,顧名思義就是多個線程可以共享同一把鎖,在JUC下面如CountDownLatch就是基於共享鎖實現的。
那麼瞭解了共享鎖是什麼,那麼先來看下它是如何獲取鎖的。
源碼
共享鎖獲取鎖和獨佔鎖獲取鎖在AQS中的邏輯是基本一致的,流程圖可以參考我上一篇中關於AQS獨佔鎖加鎖的流程圖。接下來先看下執行代碼:
acquireShared:
public final void acquireShared(int arg) {
// 獲取鎖方法由各自子類實現
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
下面的方法和獨佔鎖一樣,在上一篇中已經對代碼有詳細說明,唯一與獨佔鎖處理邏輯不一樣的地方在於setHeadAndPropagate方法,獨佔鎖此處只是將獲取到鎖的節點設置爲了頭節點,但是共享鎖不僅將獲取到鎖的節點設置爲了頭節點,還會喚醒下一個節點。那麼我們重點看下setHeadAndPropagate這個方法。
doAcquireShared:
private void doAcquireShared(int arg) {
// 將節點添加到等待隊列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 獲取當前節點的前繼節點,如果爲頭節點,那麼就有機會獲取鎖。
final Node p = node.predecessor();
if (p == head) {
// 競爭鎖,當返回值<0的時候,那麼就是獲取鎖失敗,反之則成功獲取鎖。
int r = tryAcquireShared(arg);
if (r >= 0) {
// 這塊是和獨佔鎖不一致的地方。獨佔鎖只是將獲得鎖的節點直接設置爲頭節點,這裏不僅設置爲頭節點,還向下傳播。
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagate:這個方法首先將獲取到鎖的節點設置爲頭節點,然後喚醒下一個節點。
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
// 將當前獲取到鎖的節點設置爲頭節點
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
// 滿足向下傳播條件
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
// 獲取到鎖的節點的next節點是共享鎖節點,那麼喚醒後續節點
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
doReleaseShared:喚醒下一個節點,之後這個節點就有機會去競爭鎖了。
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 釋放h.next節點
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
實現原理
1、線程A請求鎖,首先去獲取鎖。若成功獲取到鎖,直接返回,否則執行doAcquireShared。假設當前沒有線程持有這把鎖,且線程A成功獲取到這把鎖,運行線程A。
2、此時線程B請求加鎖,同樣也會嘗試獲取鎖,那麼正常此時獲取鎖失敗(A線程持有了這把鎖)。執行doAcquireShared。
3、doAcquireShared是一個死循環,在doAcquireShared中將請求B加入等待隊列,在加入前,等待隊列爲空,加入後,等待隊列如圖1所示。
4、此時B線程會判斷在等待隊列中B線程的前繼節點是不是head,如果是,則在此獲取鎖。目前的情況假設A還沒有釋放線程,B依然拿不到鎖。那麼進入到下一次循環,因爲doAcquireShared是一個死循環,拿不到鎖就一直阻塞在這個方法中。
5、此時假設A還未釋放鎖,那麼當線程C請求過來的時候,同樣會經歷步驟2和3,然後依然會判斷線程C對應的節點的前繼節點是不是頭結點,此時肯定不是頭結點,C的前繼節點是B節點,那麼會執行shouldParkAfterFailedAcquire方法。
6、在shouldParkAfterFailedAcquire方法中,首先會判斷C節點的前繼節點(也就是B節點)的狀態是不是SIGNAL(表示存在後繼節點需要被喚醒),此時節點B的狀態爲0(初始化狀態),當狀態爲0是,會將B節點的狀態置爲SIGNAL,並且返回false。
7、此時C線程請求返回到doAcquireShared方法,因爲返回了false,所以根據doAcquireShared方法的邏輯,會進入下一次循環(因爲是死循環)。此時C執行下一次循環後,又會執行到shouldParkAfterFailedAcquire方法(因爲C節點的前繼節點還不是head),但此時執行shouldParkAfterFailedAcquire方法會返回true(因爲前繼節點B的狀態在上面已經設置爲SIGNAL)了。返回true後,在doAcquireShared方法中會執行parkAndCheckInterrupt方法,阻塞當前線程,也就是線程C被阻塞掛起了,等待其他線程來解阻塞。
綜上,此時線程A持有鎖,線程B在自旋,狀態爲SIGNAL,並且一直嘗試獲取鎖,線程C阻塞掛起,狀態爲0。
AQS釋放共享鎖
AQS釋放共享鎖和上一篇講的獨佔鎖處理是基本一致的,都是喚醒後續節點。
1、假設此時A線程執行結束,釋放鎖。當A釋放鎖成功後,會執行doReleaseShared方法。
2 、在doReleaseShared中,主要是釋放下一個節點,讓它有機會去競爭鎖,這裏也就是喚醒B線程。
AQS可打斷共享鎖
acquireSharedInterruptibly:這個方法與共享鎖獲取鎖邏輯基本一致,唯一不同的是在嘗試獲取鎖之前,會判斷當前線程的打斷狀態,如果當前線程線程被打斷,那麼直接拋出異常。
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 判斷當前線程是否被打斷,如果打斷,那麼直接拋出異常
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
釋放鎖的流程和共享鎖是一致的。
AQS超時模式獨佔鎖
在獲取鎖的時候可以傳入一個超時時間,當自旋等待的時間超過傳入的等待時間時,那麼返回獲取鎖失敗,然後直接取消該線程所屬節點(也就是將在等待隊列中的該節點的狀態設置爲canceled)。
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
// 會判斷等待是否超時,如果超時就直接返回,不會在這裏自旋等待。
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
// 如果獲取鎖失敗,可能是等待超時,那麼直接將該節點設置爲取消狀態。
if (failed)
cancelAcquire(node);
}
}
重點看下上面帶註釋的兩行代碼,分別爲判斷等待時間是否超過傳入的等待時間,也就是等待是否超時,如果等待超時,那麼直接返回獲取鎖失敗,並取消該節點。其餘的處理和獨佔鎖獲取鎖流程是完全一致的。
條件等待隊列
我們先看下面的代碼實例:
static ReentrantLock reentrantLock = new ReentrantLock();
static Condition condition = reentrantLock.newCondition();
static int a = 0;
public static void add() {
reentrantLock.lock();
while (a == 0) {
try {
a++;
System.out.println("add, a="+a);
condition.signal();
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
reentrantLock.unlock();
}
public static void sub() {
reentrantLock.lock();
while (a == 1) {
try {
a--;
System.out.println("sub, a="+a);
condition.signal();
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
reentrantLock.unlock();
}
public static void main(String[] args) throws Exception{
new Thread(new Runnable() {
@Override
public void run() {
add();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
sub();
}
}).start();
}
上面的代碼很簡單,是個簡答的生產者消費者模型。其中ReentrantLock是獨佔鎖,這個我們後面會有單獨的文章來講解。下面想說的是reentrantLock.newCondition(),那麼Condition是什麼呢?下面我們就來說一下。
Condition是AQS中的另一個隊列,之前我們說過的隊列是AQS等待隊列,現在我們要說的也是AQS類中的一個隊列,叫條件等待隊列。首先當前線程要先獲取到鎖,當滿足指定條件(程序員自己指定)時,可通過signal來喚醒其他線程來獲取鎖,然後通過await方法將當前線程阻塞,這個就是條件隊列的作用,也就是可以切換不同線程來獲取同一把鎖。在AQS的源碼中定義如下:
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
// 頭節點
private transient Node firstWaiter;
/** Last node of condition queue. */
// 尾節點
private transient Node lastWaiter;
/**
* Creates a new {@code ConditionObject} instance.
*/
public ConditionObject() { }
// Internal methods
/**
* Adds a new waiter to wait queue.
* @return its new wait node
*/
// 向隊列中加入節點,狀態爲CONDITION
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
/**
* Removes and transfers nodes until hit non-cancelled one or
* null. Split out from signal in part to encourage compilers
* to inline the case of no waiters.
* @param first (non-null) the first node on condition queue
*/
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
/**
* Removes and transfers all nodes.
* @param first (non-null) the first node on condition queue
*/
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
/**
* Moves the longest-waiting thread, if one exists, from the
* wait queue for this condition to the wait queue for the
* owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
... ...
}
上面代碼就是在AQS源碼中定義的條件等待隊列,包含了頭節點和尾節點兩個Node,這裏其實就是一個雙向鏈表。至於signal和await方法的原理,我將在後面的文章中專門來詳細講解,這裏只需知道在AQS中不僅定義了一個等待隊列,還定義了一個條件等待隊列來供子類實現更多的功能(喚醒其他線程,阻塞當前線程)。
最後
本文包括上一篇AQS文章基本把AQS的總體結構及源碼都介紹了一下,大家應該對AQS有了一個總體印象。接下來的本系列文章將分析JUC包下面的具體實現類,如ReentrantLock、CountDownLatch、CyclicBarrier、Semaphore等。