AQS 的實現(xiàn)原理
學完用 AQS 自定義一個鎖以后透揣,我們可以來看一下剛剛使用過的方法的實現(xiàn)猖腕。
分析源碼的時候會省略一些不重要的代碼倘感。
AQS 的實現(xiàn)是基于一個 FIFO 隊列的老玛,每一個等待的線程被封裝成Node存放在等待隊列中蜡豹,頭結(jié)點是空的愚战,不存儲信息寂玲,等待隊列中的節(jié)點都是阻塞的想许,并且在每次被喚醒后都會檢測自己的前一個節(jié)點是否為頭結(jié)點流纹,如果是頭節(jié)點證明在這個線程之前沒有在等待的線程漱凝,就嘗試著去獲取共享資源挚币。
AQS 的繼承關(guān)系
AQS 繼承了AbstractOwnableSynchronizer,我們先分析一下這個父類笛粘。
public abstract class AbstractOwnableSynchronizer
? ? implements java.io.Serializable {
? ? protected AbstractOwnableSynchronizer() { }
? ? /**
? ? * 獨占模式下的線程
? ? */
? ? private transient Thread exclusiveOwnerThread;
? ? /**
? ? * 設(shè)置線程,只是對線程的 set 方法
? ? */
? ? protected final void setExclusiveOwnerThread(Thread thread) {
? ? ? ? exclusiveOwnerThread = thread;
? ? }
? ? /**
? ? * 設(shè)置線程,對線程的 get 方法
? ? */
? ? protected final Thread getExclusiveOwnerThread() {
? ? ? ? return exclusiveOwnerThread;
? ? }
}
父類非常簡單垛膝,持有一個獨占模式下的線程吼拥,然后就只剩下對這個線程的 get 和 set 方法。
AQS的內(nèi)部類
AQS 是用鏈表隊列來實現(xiàn)線程等待的枯跑,那么隊列肯定要有節(jié)點全肮,我們先從節(jié)點講起乍恐。
Node 類百匆,每一個等待的線程都會被封裝成 Node 類
Node 的域
public class Node {
? ? int waitStatus;
? ? Node prev;
? ? Node next;
? ? Thread thread;
? ? Node nextWaiter;
}
waitStatus:等待狀態(tài)
prev:前驅(qū)節(jié)點
next:后繼節(jié)點
thread:持有的線程
nextWaiter:condiction 隊列中的后繼節(jié)點
Node 的 status:
Node 的狀態(tài)有四種:
CANCELLED加匈,值為 1,表示當前的線程被取消啥寇,被打斷或者獲取超時了
SIGNAL辑甜,值為 -1,表示當前節(jié)點的后繼節(jié)點包含的線程需要運行子檀,也就是 unpark;
CONDITION缩歪,值為 -2,表示當前節(jié)點在等待 condition逛球,也就是在 condition 隊列中;
PROPAGATE奥务,值為 -3,表示當前場景下后續(xù)的 acquireShared 能夠得以執(zhí)行帚称;
取消狀態(tài)的值是唯一的正數(shù),也是唯一當排隊排到它了也不要資源而是直接輪到下個線程來獲取資源的
AQS 中的方法源碼分析
acquire
這個方法執(zhí)行了:
tryAcquire
public final void acquire(int arg) {
? ? if (!tryAcquire(arg) &&
? ? ? ? acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
? ? ? ? selfInterrupt();
}
看到上面的 tryAcquire 返回 false 后就會調(diào)用addWaiter新建節(jié)點加入等待隊列中。參數(shù) EXCLUSIVE 是獨占模式所刀。
private Node addWaiter(Node mode) {
? ? Node node = new Node(Thread.currentThread(), mode);
? ? // 拿到尾節(jié)點,如果尾節(jié)點是空則說明是第一個節(jié)點溜族,就直接入隊就好了
? ? Node pred = tail;
? ? if (pred != null) {
? ? ? ? node.prev = pred;
? ? ? ? if (compareAndSetTail(pred, node)) {
? ? ? ? ? ? pred.next = node;
? ? ? ? ? ? return node;
? ? ? ? }
? ? }
? ? // 如果尾節(jié)點不是空的,則需要特殊方法入隊
? ? ? ? enq(node);
? ? return node;
}
在addWaiter方法創(chuàng)建完節(jié)點后寡壮,調(diào)用 enq 方法况既,在循環(huán)中用 CAS 操作將新的節(jié)點入隊。
因為可能會有多個線程同時設(shè)置尾節(jié)點莫其,所以需要放在循環(huán)中不斷的設(shè)置尾節(jié)點。
private Node enq(final Node node) {
? ? for (;;) {
? ? ? ? Node t = tail;
? ? ? ? // 查看尾節(jié)點
? ? ? ? if (t == null) { // Must initialize
? ? ? ? ? ? // 尾節(jié)點為空則設(shè)置為剛剛創(chuàng)建的節(jié)點
? ? ? ? ? ? if (compareAndSetHead(new Node()))
? ? ? ? ? ? ? ? tail = head;
? ? ? ? } else {
? ? ? ? ? ? // 尾節(jié)點
? ? ? ? ? ? node.prev = t;
? ? ? ? ? ? if (compareAndSetTail(t, node)) {
? ? ? ? ? ? ? ? t.next = node;
? ? ? ? ? ? ? ? return t;
? ? ? ? ? ? }
? ? ? ? }
? ? }
}
在這里睛驳,節(jié)點入隊就結(jié)束了膜廊。
那么我們回來前面分析的方法,
public final void acquire(long arg) {
? ? if (!tryAcquire(arg) &&
? ? ? ? acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
? ? ? ? selfInterrupt();
}
剛剛分析完了addWaiter方法,這個方法返回了剛剛創(chuàng)建并且加入的隊列”』酰現(xiàn)在開始分析acquireQueued方法谅猾。
final boolean acquireQueued(final Node node, int arg) {
? ? boolean failed = true;
? ? try {
? ? ? ? boolean interrupted = false;
? ? ? ? // 在循環(huán)中去獲取鎖
? ? ? ? for (;;) {
? ? ? ? ? ? // 拿前一個節(jié)點
? ? ? ? ? ? final Node p = node.predecessor();
? ? ? ? ? ? // 如果前一個節(jié)點是頭結(jié)點則嘗試著去獲取鎖
? ? ? ? ? ? if (p == head && tryAcquire(arg)) {
? ? ? ? ? ? ? ? // 拿到鎖后把這個節(jié)點設(shè)為頭結(jié)點,這里 setHead 會把除了 next 以外的數(shù)據(jù)清除
? ? ? ? ? ? ? ? setHead(node);
? ? ? ? ? ? ? ? p.next = null; // help GC
? ? ? ? ? ? ? ? failed = false;
? ? ? ? ? ? ? ? return interrupted;
? ? ? ? ? ? }
? ? ? ? ? ? // 這個方法查看在獲取鎖失敗以后是否中斷,如果否的話就調(diào)用
? ? ? ? ? ? // parkAndCheckInterrupt 阻塞方法線程谤绳,等待被喚醒
? ? ? ? ? ? if (shouldParkAfterFailedAcquire(p, node) &&
? ? ? ? ? ? ? ? parkAndCheckInterrupt())
? ? ? ? ? ? ? ? interrupted = true;
? ? ? ? }
? ? } finally {
? ? ? ? if (failed)
? ? ? ? ? ? cancelAcquire(node);
? ? }
}
acquireInterruptibly
因為很像所以順便來看一下acquireInterruptibly所調(diào)用的方法:在此我向大家推薦一個架構(gòu)學習交流裙堡称。交流學習裙號:821169538,里面會分享一些資深架構(gòu)師錄制的視頻錄像
private void doAcquireInterruptibly(int arg)
? ? throws InterruptedException {
? ? 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;
? ? ? ? ? ? }
? ? ? ? ? ? // 只有這一句有差別,獲取失敗了并且檢測到中斷位被設(shè)為 true 直接拋出異常
? ? ? ? ? ? if (shouldParkAfterFailedAcquire(p, node) &&
? ? ? ? ? ? ? ? parkAndCheckInterrupt())
? ? ? ? ? ? ? ? throw new InterruptedException();
? ? ? ? }
? ? } finally {
? ? ? ? if (failed)
? ? ? ? ? ? cancelAcquire(node);
? ? }
}
acquireNanos
再來看一下有限時間的,當獲取超時以后會將節(jié)點 Node 的狀態(tài)設(shè)為 cancel肿男,設(shè)置為取消的用處在后面的 release 方法中會有體現(xiàn)窗价。
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;
? ? ? ? ? ? ? ? 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);
? ? }
}
總結(jié)一下過程
?
release
這個方法首先去調(diào)用了我們實現(xiàn)的 tryRelease帝牡,當結(jié)果返回成功的時候否灾,拿到頭結(jié)點墨技,調(diào)用 unparkSuccessor 方法來喚醒頭結(jié)點的下一個節(jié)點。在此我向大家推薦一個架構(gòu)學習交流裙。交流學習裙號:821169538茅主,里面會分享一些資深架構(gòu)師錄制的視頻錄像?
public final boolean release(long arg) {
? ? if (tryRelease(arg)) {
? ? ? ? Node h = head;
? ? ? ? if (h != null && h.waitStatus != 0)
? ? ? ? ? ? unparkSuccessor(h);
? ? ? ? return true;
? ? }
? ? return false;
}
private void unparkSuccessor(Node node) {
? ? int ws = node.waitSatus;
? ? // 因為已經(jīng)獲取過鎖,所以將狀態(tài)設(shè)設(shè)為 0。失敗也沒所謂,說明有其他的線程把它設(shè)為0了
? ? if (ws < 0)
? ? ? ? compareAndSetWaitStatus(node, ws, 0);
? ? /*
? ? * 一般來說頭結(jié)點的下一個節(jié)點是在等待著被喚醒的,但是如果是取消的或者意外的是空的给涕,
? ? * 則向后遍歷直到找到?jīng)]有被取消的節(jié)點
? ? *
? ? */
? ? Node s = node.next;
? ? // 為空或者大于 0稠炬,只有 cancel 狀態(tài)是大于 0 的
? ? 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);
}