摘要:本篇我們將以的公平鎖為例來(lái)詳細(xì)看看使用獲取獨(dú)占鎖的流程。本文中的源碼基于。由于本篇我們分析的是獨(dú)占鎖,同一時(shí)刻,鎖只能被一個(gè)線程所持有。由于在整個(gè)搶鎖過(guò)程中,我們都是不響應(yīng)中斷的。
前言
AQS(AbstractQueuedSynchronizer)是JAVA中眾多鎖以及并發(fā)工具的基礎(chǔ),其底層采用樂(lè)觀鎖,大量使用了CAS操作, 并且在沖突時(shí),采用自旋方式重試,以實(shí)現(xiàn)輕量級(jí)和高效地獲取鎖。
AQS雖然被定義為抽象類,但事實(shí)上它并不包含任何抽象方法。這是因?yàn)锳QS是被設(shè)計(jì)來(lái)支持多種用途的,如果定義抽象方法,則子類在繼承時(shí)必須要覆寫(xiě)所有的抽象方法,這顯然是不合理的。所以AQS將一些需要子類覆寫(xiě)的方法都設(shè)計(jì)成protect方法,將其默認(rèn)實(shí)現(xiàn)為拋出UnsupportedOperationException異常。如果子類使用到這些方法,但是沒(méi)有覆寫(xiě),則會(huì)拋出異常;如果子類沒(méi)有使用到這些方法,則不需要做任何操作。
AQS中實(shí)現(xiàn)了鎖的獲取框架,鎖的實(shí)際獲取邏輯交由子類去實(shí)現(xiàn),就鎖的獲取操作而言,子類必須重寫(xiě) tryAcquire方法。
本篇我們將以ReentrantLock的公平鎖為例來(lái)詳細(xì)看看使用AQS獲取獨(dú)占鎖的流程。
本文中的源碼基于JDK1.8 。
系列文章目錄
Java并發(fā)工具類的三板斧在開(kāi)始看AQS源碼之前,我們先來(lái)了解以下java并發(fā)工具的設(shè)計(jì)套路,我把它總結(jié)成三板斧:
狀態(tài),隊(duì)列,CAS
每當(dāng)我們學(xué)習(xí)一個(gè)java并發(fā)編程工具的時(shí)候,我們首先要抓住這三點(diǎn)。
狀態(tài):一般是一個(gè)state屬性,它基本是整個(gè)工具的核心,通常整個(gè)工具都是在設(shè)置和修改狀態(tài),很多方法的操作都依賴于當(dāng)前狀態(tài)是什么。由于狀態(tài)是全局共享的,一般會(huì)被設(shè)置成volatile類型,以保證其修改的可見(jiàn)性;
隊(duì)列:隊(duì)列通常是一個(gè)等待的集合,大多數(shù)以鏈表的形式實(shí)現(xiàn)。隊(duì)列采用的是悲觀鎖的思想,表示當(dāng)前所等待的資源,狀態(tài)或者條件短時(shí)間內(nèi)可能無(wú)法滿足。因此,它會(huì)將當(dāng)前線程包裝成某種類型的數(shù)據(jù)結(jié)構(gòu),扔到一個(gè)等待隊(duì)列中,當(dāng)一定條件滿足后,再?gòu)牡却?duì)列中取出。
CAS: CAS操作是最輕量的并發(fā)處理,通常我們對(duì)于狀態(tài)的修改都會(huì)用到CAS操作,因?yàn)闋顟B(tài)可能被多個(gè)線程同時(shí)修改,CAS操作保證了同一個(gè)時(shí)刻,只有一個(gè)線程能修改成功,從而保證了線程安全,CAS操作基本是由Unsafe工具類的compareAndSwapXXX來(lái)實(shí)現(xiàn)的;CAS采用的是樂(lè)觀鎖的思想,因此常常伴隨著自旋,如果發(fā)現(xiàn)當(dāng)前無(wú)法成功地執(zhí)行CAS,則不斷重試,直到成功為止,自旋的的表現(xiàn)形式通常是一個(gè)死循環(huán)for(;;)。
AQS核心實(shí)現(xiàn)上面我們已經(jīng)總結(jié)了java并發(fā)編程的套路,下面我們就以這個(gè)套路為切入點(diǎn)來(lái)分析AQS的實(shí)現(xiàn)。
狀態(tài)首先是找狀態(tài)。
在AQS中,狀態(tài)是由state屬性來(lái)表示的,不出所料,它是volatile類型的:
private volatile int state;
該屬性的值即表示了鎖的狀態(tài),state為0表示鎖沒(méi)有被占用,state大于0表示當(dāng)前已經(jīng)有線程持有該鎖,這里之所以說(shuō)大于0而不說(shuō)等于1是因?yàn)榭赡艽嬖诳芍厝氲那闆r。你可以把state變量當(dāng)做是當(dāng)前持有該鎖的線程數(shù)量。
由于本篇我們分析的是獨(dú)占鎖,同一時(shí)刻,鎖只能被一個(gè)線程所持有。通過(guò)state變量是否為0,我們可以分辨當(dāng)前鎖是否被占用,但光知道鎖是不是被占用是不夠的,我們并不知道占用鎖的線程是哪一個(gè)。在監(jiān)視器鎖中,我們用ObjectMonitor對(duì)象的_owner屬性記錄了當(dāng)前擁有監(jiān)視器鎖的線程,而在AQS中,我們將通過(guò)exclusiveOwnerThread屬性:
private transient Thread exclusiveOwnerThread; //繼承自AbstractOwnableSynchronizer
exclusiveOwnerThread屬性的值即為當(dāng)前持有鎖的線程,它就是我們?cè)诜治霰O(jiān)視器鎖的原理的時(shí)候所說(shuō)的“鐵王座”。
隊(duì)列接著我們來(lái)看隊(duì)列,AQS中,隊(duì)列的實(shí)現(xiàn)是一個(gè)雙向鏈表,被稱為sync queue,它表示所有等待鎖的線程的集合,有點(diǎn)類似于我們前面介紹synchronized原理的時(shí)候說(shuō)的wait set。
我們前面說(shuō)過(guò),在并發(fā)編程中使用隊(duì)列通常是將當(dāng)前線程包裝成某種類型的數(shù)據(jù)結(jié)構(gòu)扔到等待隊(duì)列中,我們先來(lái)看看隊(duì)列中的每一個(gè)節(jié)點(diǎn)是怎么個(gè)結(jié)構(gòu):
static final class Node { /** Marker to indicate a node is waiting in shared mode */ static final Node SHARED = new Node(); /** Marker to indicate a node is waiting in exclusive mode */ static final Node EXCLUSIVE = null; /** waitStatus value to indicate thread has cancelled */ static final int CANCELLED = 1; /** waitStatus value to indicate successor"s thread needs unparking */ static final int SIGNAL = -1; /** waitStatus value to indicate thread is waiting on condition */ static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ static final int PROPAGATE = -3; volatile int waitStatus; volatile Node prev; volatile Node next; volatile Thread thread; Node nextWaiter; final boolean isShared() { return nextWaiter == SHARED; } final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } Node() { // Used to establish initial head or SHARED marker } Node(Thread thread, Node mode) { // Used by addWaiter this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { // Used by Condition this.waitStatus = waitStatus; this.thread = thread; } }
這個(gè)結(jié)構(gòu)看起來(lái)很復(fù)雜,其實(shí)屬性只有4類:
// 節(jié)點(diǎn)所代表的線程 volatile Thread thread; // 雙向鏈表,每個(gè)節(jié)點(diǎn)需要保存自己的前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)的引用 volatile Node prev; volatile Node next; // 線程所處的等待鎖的狀態(tài),初始化時(shí),該值為0 volatile int waitStatus; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; // 該屬性用于條件隊(duì)列或者共享鎖 Node nextWaiter;
注意,在這個(gè)Node類中也有一個(gè)狀態(tài)變量waitStatus,它表示了當(dāng)前Node所代表的線程的等待鎖的狀態(tài),在獨(dú)占鎖模式下,我們只需要關(guān)注CANCELLED SIGNAL兩種狀態(tài)即可。這里還有一個(gè)nextWaiter屬性,它在獨(dú)占鎖模式下永遠(yuǎn)為null,僅僅起到一個(gè)標(biāo)記作用,沒(méi)有實(shí)際意義。這么一分析,這個(gè)Node類是不是就簡(jiǎn)單了好多?<( ̄︶ ̄)>
說(shuō)完隊(duì)列中的節(jié)點(diǎn),我們接著說(shuō)回這個(gè)sync queue,AQS是怎么使用這個(gè)隊(duì)列的呢,既然是雙向鏈表,操縱它自然只需要一個(gè)頭結(jié)點(diǎn)和一個(gè)尾節(jié)點(diǎn):
// 頭結(jié)點(diǎn),不代表任何線程,是一個(gè)啞結(jié)點(diǎn) private transient volatile Node head; // 尾節(jié)點(diǎn),每一個(gè)請(qǐng)求鎖的線程會(huì)加到隊(duì)尾 private transient volatile Node tail;
到這里,我們就了解到了這個(gè)sync queue的全貌:
不過(guò)這里有一點(diǎn)我們提前說(shuō)一下,在AQS中的隊(duì)列是一個(gè)CLH隊(duì)列,它的head節(jié)點(diǎn)永遠(yuǎn)是一個(gè)啞結(jié)點(diǎn)(dummy node), 它不代表任何線程(某些情況下可以看做是代表了當(dāng)前持有鎖的線程),因此head所指向的Node的thread屬性永遠(yuǎn)是null。只有從次頭節(jié)點(diǎn)往后的所有節(jié)點(diǎn)才代表了所有等待鎖的線程。也就是說(shuō),在當(dāng)前線程沒(méi)有搶到鎖被包裝成Node扔到隊(duì)列中時(shí),即使隊(duì)列是空的,它也會(huì)排在第二個(gè),我們會(huì)在它的前面新建一個(gè)dummy節(jié)點(diǎn)(具體的代碼我們?cè)诤竺娣治鲈创a時(shí)再詳細(xì)講)。為了便于描述,下文中我們把除去head節(jié)點(diǎn)的隊(duì)列稱作是等待隊(duì)列,在這個(gè)隊(duì)列中的節(jié)點(diǎn)才代表了所有等待鎖的線程:
在繼續(xù)往下之前我們?cè)賹?duì)著上圖總結(jié)一下Node節(jié)點(diǎn)各個(gè)參數(shù)的含義:
thread:表示當(dāng)前Node所代表的線程
waitStatus:表示節(jié)點(diǎn)所處的等待狀態(tài),共享鎖模式下只需關(guān)注三種狀態(tài):SIGNAL CANCELLED 初始態(tài)(0)
prev next:節(jié)點(diǎn)的前驅(qū)和后繼
nextWaiter:進(jìn)作為標(biāo)記,值永遠(yuǎn)為null,表示當(dāng)前處于獨(dú)占鎖模式
CAS操作前面我們提到過(guò),CAS操作大對(duì)數(shù)是用來(lái)改變狀態(tài)的,在AQS中也不例外。我們一般在靜態(tài)代碼塊中初始化需要CAS操作的屬性的偏移量:
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long stateOffset; private static final long headOffset; private static final long tailOffset; private static final long waitStatusOffset; private static final long nextOffset; static { try { stateOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("state")); headOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("head")); tailOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("tail")); waitStatusOffset = unsafe.objectFieldOffset (Node.class.getDeclaredField("waitStatus")); nextOffset = unsafe.objectFieldOffset (Node.class.getDeclaredField("next")); } catch (Exception ex) { throw new Error(ex); } }
從這個(gè)靜態(tài)代碼塊中我們也可以看出,CAS操作主要針對(duì)5個(gè)屬性,包括AQS的3個(gè)屬性state,head和tail, 以及Node對(duì)象的兩個(gè)屬性waitStatus,next。說(shuō)明這5個(gè)屬性基本是會(huì)被多個(gè)線程同時(shí)訪問(wèn)的。
定義完屬性的偏移量之后,接下來(lái)就是CAS操作本身了:
protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } private final boolean compareAndSetHead(Node update) { return unsafe.compareAndSwapObject(this, headOffset, null, update); } private final boolean compareAndSetTail(Node expect, Node update) { return unsafe.compareAndSwapObject(this, tailOffset, expect, update); } private static final boolean compareAndSetWaitStatus(Node node, int expect,int update) { return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update); } private static final boolean compareAndSetNext(Node node, Node expect, Node update) { return unsafe.compareAndSwapObject(node, nextOffset, expect, update); }
如前面所說(shuō),最終CAS操作調(diào)用的還是Unsafe類的compareAndSwapXXX方法。
最后就是自旋了,這一點(diǎn)就沒(méi)有什么好說(shuō)的了,我們?cè)诤竺嬖创a分析的時(shí)候再詳細(xì)講。
AQS核心屬性前面我們以java并發(fā)編程工具類的“三板斧”為切入點(diǎn)分析了AQS的狀態(tài),隊(duì)列和CAS操作,對(duì)這個(gè)工具類有了初步的認(rèn)識(shí)。接下來(lái),我們就要開(kāi)始進(jìn)入源碼分析了。在進(jìn)入正式的分析之前,我們先來(lái)總結(jié)下AQS核心屬性:
(1)鎖相關(guān)的屬性有兩個(gè):
private volatile int state; //鎖的狀態(tài) private transient Thread exclusiveOwnerThread; // 當(dāng)前持有鎖的線程,注意這個(gè)屬性是從AbstractOwnableSynchronizer繼承而來(lái)
(2)sync queue相關(guān)的屬性有兩個(gè):
private transient volatile Node head; // 隊(duì)頭,為dummy node private transient volatile Node tail; // 隊(duì)尾,新入隊(duì)的節(jié)點(diǎn)
(3)隊(duì)列中的Node中需要關(guān)注的屬性有三組:
// 節(jié)點(diǎn)所代表的線程 volatile Thread thread; // 雙向鏈表,每個(gè)節(jié)點(diǎn)需要保存自己的前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)的引用 volatile Node prev; volatile Node next; // 線程所處的等待鎖的狀態(tài),初始化時(shí),該值為0 volatile int waitStatus; static final int CANCELLED = 1; static final int SIGNAL = -1;
拎了這些屬性后,我們下面分析源碼就容易很多了。
Example: FairSync in ReentrantLock前面已經(jīng)提到, AQS大多數(shù)情況下都是通過(guò)繼承來(lái)使用的, 子類通過(guò)覆寫(xiě) tryAcquire 來(lái)實(shí)現(xiàn)自己的獲取鎖的邏輯,我們這里以ReentrantLock為例來(lái)說(shuō)明鎖的獲取流程。
值得注意的是, ReentrantLock有 公平鎖 和 非公平鎖 兩種實(shí)現(xiàn), 默認(rèn)實(shí)現(xiàn)為非公平鎖, 這體現(xiàn)在它的構(gòu)造函數(shù)中:
public class ReentrantLock implements Lock, java.io.Serializable { /** Synchronizer providing all implementation mechanics */ private final Sync sync; /** * Base of synchronization control for this lock. Subclassed * into fair and nonfair versions below. Uses AQS state to * represent the number of holds on the lock. */ abstract static class Sync extends AbstractQueuedSynchronizer { ... } /** * Sync object for non-fair locks */ static final class NonfairSync extends Sync{ ... } /** * Sync object for fair locks */ static final class FairSync extends Sync { ... } /** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); } /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } // 獲取鎖 public void lock() { sync.lock(); } ... }
可以看出, FairSync 繼承自 Sync, 而Sync繼承自 AQS, ReentrantLock獲取鎖的邏輯是直接調(diào)用了 FairSync 或者 NonfairSync的邏輯.
好了, ReentrantLock 就簡(jiǎn)單說(shuō)到這里, 以后我們有機(jī)會(huì)再詳細(xì)講, 這里直接以 FairLock 為例, 來(lái)逐行分析鎖的獲取:
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; //獲取鎖 final void lock() { acquire(1); } ... }
lock 方法調(diào)用的 acquire方法來(lái)自父類AQS。
這里首先給出完整的獲取鎖的流程圖, 再逐行分析代碼, 因?yàn)榭丛创a的時(shí)候, 代碼會(huì)在函數(shù)或者循環(huán)中來(lái)回跳轉(zhuǎn),讀者可以對(duì)照以下流程圖, 就不容易被繞暈了.
acquireacquire 定義在AQS類中,描述了獲取鎖的流程
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
可以看出, 該方法中涉及了四個(gè)方法的調(diào)用:
(1)tryAcquire(arg)
該方法由繼承AQS的子類實(shí)現(xiàn), 為獲取鎖的具體邏輯。
(2)addWaiter(Node mode)
該方法由AQS實(shí)現(xiàn), 負(fù)責(zé)在獲取鎖失敗后調(diào)用, 將當(dāng)前請(qǐng)求鎖的線程包裝成Node扔到sync queue中去,并返回這個(gè)Node。
(3)acquireQueued(final Node node, int arg)
該方法由AQS實(shí)現(xiàn),這個(gè)方法比較復(fù)雜, 主要對(duì)上面剛加入隊(duì)列的Node不斷嘗試以下兩種操作之一:
在前驅(qū)節(jié)點(diǎn)就是head節(jié)點(diǎn)的時(shí)候,繼續(xù)嘗試獲取鎖
將當(dāng)前線程掛起,使CPU不再調(diào)度它
(4)selfInterrupt
該方法由AQS實(shí)現(xiàn), 用于中斷當(dāng)前線程。由于在整個(gè)搶鎖過(guò)程中,我們都是不響應(yīng)中斷的。那如果在搶鎖的過(guò)程中發(fā)生了中斷怎么辦呢,總不能假裝沒(méi)看見(jiàn)呀。AQS的做法簡(jiǎn)單的記錄有沒(méi)有有發(fā)生過(guò)中斷,如果返回的時(shí)候發(fā)現(xiàn)曾經(jīng)發(fā)生過(guò)中斷,則在退出acquire方法之前,就調(diào)用selfInterrupt自我中斷一下,就好像將這個(gè)發(fā)生在搶鎖過(guò)程中的中斷“推遲”到搶鎖結(jié)束以后再發(fā)生一樣。
從上面的簡(jiǎn)單介紹中可以看出,除了獲取鎖的邏輯 tryAcquire(arg)由子類實(shí)現(xiàn)外, 其余方法均由AQS實(shí)現(xiàn)。
接下來(lái)我們重點(diǎn)來(lái)看 FairSync 所實(shí)現(xiàn)的獲取鎖的邏輯:
tryAcquiretryAcquire 獲取鎖的邏輯其實(shí)很簡(jiǎn)單——判斷當(dāng)前鎖有沒(méi)有被占用:
如果鎖沒(méi)有被占用, 嘗試以公平的方式獲取鎖
如果鎖已經(jīng)被占用, 檢查是不是鎖重入
獲取鎖成功返回true, 失敗則返回false
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 首先獲取當(dāng)前鎖的狀態(tài) int c = getState(); // c=0 說(shuō)明當(dāng)前鎖是avaiable的, 沒(méi)有被任何線程占用, 可以嘗試獲取 // 因?yàn)槭菍?shí)現(xiàn)公平鎖, 所以在搶占之前首先看看隊(duì)列中有沒(méi)有排在自己前面的Node // 如果沒(méi)有人在排隊(duì), 則通過(guò)CAS方式獲取鎖, 就可以直接退出了 if (c == 0) { if (!hasQueuedPredecessors() /* 為了閱讀方便, hasQueuedPredecessors源碼就直接貼在這里了, 這個(gè)方法的本質(zhì)實(shí)際上是檢測(cè)自己是不是head節(jié)點(diǎn)的后繼節(jié)點(diǎn),即處在阻塞隊(duì)列第一位的節(jié)點(diǎn) public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); } */ && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); // 將當(dāng)前線程設(shè)置為占用鎖的線程 return true; } } // 如果 c>0 說(shuō)明鎖已經(jīng)被占用了 // 對(duì)于可重入鎖, 這個(gè)時(shí)候檢查占用鎖的線程是不是就是當(dāng)前線程,是的話,說(shuō)明已經(jīng)拿到了鎖, 直接重入就行 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); /* setState方法如下: protected final void setState(int newState) { state = newState; } */ return true; } // 到這里說(shuō)明有人占用了鎖, 并且占用鎖的不是當(dāng)前線程, 則獲取鎖失敗 return false; }
從這里可以看出,獲取鎖其實(shí)主要就是干一件事:
將state的狀態(tài)通過(guò)CAS操作由0改寫(xiě)成1
由于是CAS操作,必然是只有一個(gè)線程能執(zhí)行成功。則執(zhí)行成功的線程即獲取了鎖,在這之后,才有權(quán)利將exclusiveOwnerThread的值設(shè)成自己,從而“坐上鐵王座”。
另外對(duì)于可重入鎖,如果當(dāng)前線程已經(jīng)是獲取了鎖的線程了,它還要注意增加鎖的重入次數(shù)。
值得一提的是,這里修改state狀態(tài)的操作,一個(gè)用了CAS方法compareAndSetState,一個(gè)用了普通的setState方法。這是因?yàn)橛肅AS操作時(shí),當(dāng)前線程還沒(méi)有獲得鎖,所以可能存在多線程同時(shí)在競(jìng)爭(zhēng)鎖的情況;而調(diào)用setState方法時(shí),是在當(dāng)前線程已經(jīng)是持有鎖的情況下,因此對(duì)state的修改是安全的,只需要普通的方法就可以了。
因此,在多線程條件下看源碼時(shí),我們一定要時(shí)刻在心中問(wèn)自己:
這段代碼是否是線程安全的?同一時(shí)刻是否可能有多個(gè)線程在執(zhí)行這行代碼?addWaiter
如果執(zhí)行到此方法, 說(shuō)明前面嘗試獲取鎖的tryAcquire已經(jīng)失敗了, 既然獲取鎖已經(jīng)失敗了, 就要將當(dāng)前線程包裝成Node,加到等待鎖的隊(duì)列中去, 因?yàn)槭荈IFO隊(duì)列, 所以自然是直接加在隊(duì)尾。
方法調(diào)用為:
addWaiter(Node.EXCLUSIVE)
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); //將當(dāng)前線程包裝成Node // 這里我們用注釋的形式把Node的構(gòu)造函數(shù)貼出來(lái) // 因?yàn)閭魅氲膍ode值為Node.EXCLUSIVE,所以節(jié)點(diǎn)的nextWaiter屬性被設(shè)為null /* static final Node EXCLUSIVE = null; Node(Thread thread, Node mode) { // Used by addWaiter this.nextWaiter = mode; this.thread = thread; } */ Node pred = tail; // 如果隊(duì)列不為空, 則用CAS方式將當(dāng)前節(jié)點(diǎn)設(shè)為尾節(jié)點(diǎn) if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 代碼會(huì)執(zhí)行到這里, 只有兩種情況: // 1. 隊(duì)列為空 // 2. CAS失敗 // 注意, 這里是并發(fā)條件下, 所以什么都有可能發(fā)生, 尤其注意CAS失敗后也會(huì)來(lái)到這里 enq(node); //將節(jié)點(diǎn)插入隊(duì)列 return node; }
可見(jiàn),每一個(gè)處于獨(dú)占鎖模式下的節(jié)點(diǎn),它的nextWaiter一定是null。
在這個(gè)方法中,我們首先會(huì)嘗試直接入隊(duì),但是因?yàn)槟壳笆窃诓l(fā)條件下,所以有可能同一時(shí)刻,有多個(gè)線程都在嘗試入隊(duì),導(dǎo)致compareAndSetTail(pred, node)操作失敗——因?yàn)橛锌赡芷渌€程已經(jīng)成為了新的尾節(jié)點(diǎn),導(dǎo)致尾節(jié)點(diǎn)不再是我們之前看到的那個(gè)pred了。
如果入隊(duì)失敗了,接下來(lái)我們就需要調(diào)用enq(node)方法,在該方法中我們將通過(guò)自旋+CAS的方式,確保當(dāng)前節(jié)點(diǎn)入隊(duì)。
enq能執(zhí)行到這個(gè)方法,說(shuō)明當(dāng)前線程獲取鎖已經(jīng)失敗了,我們已經(jīng)把它包裝成一個(gè)Node,準(zhǔn)備把它扔到等待隊(duì)列中去,但是在這一步又失敗了。這個(gè)失敗的原因可能是以下兩種之一:
等待隊(duì)列現(xiàn)在是空的,沒(méi)有線程在等待。
其他線程在當(dāng)前線程入隊(duì)的過(guò)程中率先完成了入隊(duì),導(dǎo)致尾節(jié)點(diǎn)的值已經(jīng)改變了,CAS操作失敗。
在該方法中, 我們使用了死循環(huán), 即以自旋方式將節(jié)點(diǎn)插入隊(duì)列,如果失敗則不停的嘗試, 直到成功為止, 另外, 該方法也負(fù)責(zé)在隊(duì)列為空時(shí), 初始化隊(duì)列,這也說(shuō)明,隊(duì)列是延時(shí)初始化的(lazily initialized):
private Node enq(final Node node) { for (;;) { Node t = tail; // 如果是空隊(duì)列, 首先進(jìn)行初始化 // 這里也可以看出, 隊(duì)列不是在構(gòu)造的時(shí)候初始化的, 而是延遲到需要用的時(shí)候再初始化, 以提升性能 if (t == null) { // 注意,初始化時(shí)使用new Node()方法新建了一個(gè)dummy節(jié)點(diǎn) if (compareAndSetHead(new Node())) tail = head; // 這里僅僅是將尾節(jié)點(diǎn)指向dummy節(jié)點(diǎn),并沒(méi)有返回 } else { // 到這里說(shuō)明隊(duì)列已經(jīng)不是空的了, 這個(gè)時(shí)候再繼續(xù)嘗試將節(jié)點(diǎn)加到隊(duì)尾 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
這里尤其要注意的是,當(dāng)隊(duì)列為空時(shí),我們初始化隊(duì)列并沒(méi)有使用當(dāng)前傳進(jìn)來(lái)的節(jié)點(diǎn),而是:
新建了一個(gè)空節(jié)點(diǎn)!!!
新建了一個(gè)空節(jié)點(diǎn)!!!
新建了一個(gè)空節(jié)點(diǎn)!!!
在新建完空的頭節(jié)點(diǎn)之后,我們并沒(méi)有立即返回,而是將尾節(jié)點(diǎn)指向當(dāng)前的頭節(jié)點(diǎn),然后進(jìn)入下一輪循環(huán)。
在下一輪循環(huán)中,尾節(jié)點(diǎn)已經(jīng)不為null了,此時(shí)再將我們包裝了當(dāng)前線程的Node加到這個(gè)空節(jié)點(diǎn)后面。
這就意味著,在這個(gè)等待隊(duì)列中,頭結(jié)點(diǎn)是一個(gè)“啞節(jié)點(diǎn)”,它不代表任何等待的線程。
head節(jié)點(diǎn)不代表任何線程,它就是一個(gè)空節(jié)點(diǎn)!!!
head節(jié)點(diǎn)不代表任何線程,它就是一個(gè)空節(jié)點(diǎn)!!!
head節(jié)點(diǎn)不代表任何線程,它就是一個(gè)空節(jié)點(diǎn)!!!
在繼續(xù)往下之前,我們先分析enq方法中一個(gè)比較有趣的現(xiàn)象,我把它叫做尾分叉。我們著重看將當(dāng)前節(jié)點(diǎn)設(shè)置成尾節(jié)點(diǎn)的操作:
} else { // 到這里說(shuō)明隊(duì)列已經(jīng)不是空的了, 這個(gè)時(shí)候再繼續(xù)嘗試將節(jié)點(diǎn)加到隊(duì)尾 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } }
與將大象放到冰箱里需要三步一樣,將一個(gè)節(jié)點(diǎn)node添加到sync queue的末尾也需要三步:
設(shè)置node的前驅(qū)節(jié)點(diǎn)為當(dāng)前的尾節(jié)點(diǎn):node.prev = t
修改tail屬性,使它指向當(dāng)前節(jié)點(diǎn)
修改原來(lái)的尾節(jié)點(diǎn),使它的next指向當(dāng)前節(jié)點(diǎn)
但是需要注意的,這里的三步并不是一個(gè)原子操作,第一步很容易成功;而第二步由于是一個(gè)CAS操作,在并發(fā)條件下有可能失敗,第三步只有在第二步成功的條件下才執(zhí)行。這里的CAS保證了同一時(shí)刻只有一個(gè)節(jié)點(diǎn)能成為尾節(jié)點(diǎn),其他節(jié)點(diǎn)將失敗,失敗后將回到for循環(huán)中繼續(xù)重試。
所以,當(dāng)有大量的線程在同時(shí)入隊(duì)的時(shí)候,同一時(shí)刻,只有一個(gè)線程能完整地完成這三步,而其他線程只能完成第一步,于是就出現(xiàn)了尾分叉:
注意,這里第三步是在第二步執(zhí)行成功后才執(zhí)行的,這就意味著,有可能即使我們已經(jīng)完成了第二步,將新的節(jié)點(diǎn)設(shè)置成了尾節(jié)點(diǎn),此時(shí)原來(lái)舊的尾節(jié)點(diǎn)的next值可能還是null(因?yàn)檫€沒(méi)有來(lái)的及執(zhí)行第三步),所以如果此時(shí)有線程恰巧從頭節(jié)點(diǎn)開(kāi)始向后遍歷整個(gè)鏈表,則它是遍歷不到新加進(jìn)來(lái)的尾節(jié)點(diǎn)的,但是這顯然是不合理的,因?yàn)楝F(xiàn)在的tail已經(jīng)指向了新的尾節(jié)點(diǎn)。
另一方面,當(dāng)我們完成了第二步之后,第一步一定是完成了的,所以如果我們從尾節(jié)點(diǎn)開(kāi)始向前遍歷,已經(jīng)可以遍歷到所有的節(jié)點(diǎn)。這也就是為什么我們?cè)贏QS相關(guān)的源碼中,有時(shí)候常常會(huì)出現(xiàn)從尾節(jié)點(diǎn)開(kāi)始逆向遍歷鏈表——因?yàn)橐粋€(gè)節(jié)點(diǎn)要能入隊(duì),則它的prev屬性一定是有值的,但是它的next屬性可能暫時(shí)還沒(méi)有值。
至于那些“分叉”的入隊(duì)失敗的其他節(jié)點(diǎn),在下一輪的循環(huán)中,它們的prev屬性會(huì)重新指向新的尾節(jié)點(diǎn),繼續(xù)嘗試新的CAS操作,最終,所有節(jié)點(diǎn)都會(huì)通過(guò)自旋不斷的嘗試入隊(duì),直到成功為止。
addWaiter總結(jié)至此,我們就完成了addWaiter(Node.EXCLUSIVE)方法的完整的分析,該方法并不設(shè)計(jì)到任何關(guān)于鎖的操作,它就是解決了并發(fā)條件下的節(jié)點(diǎn)入隊(duì)問(wèn)題。具體來(lái)說(shuō)就是該方法保證了將當(dāng)前線程包裝成Node節(jié)點(diǎn)加入到等待隊(duì)列的隊(duì)尾,如果隊(duì)列為空,則會(huì)新建一個(gè)啞節(jié)點(diǎn)作為頭節(jié)點(diǎn),再將當(dāng)前節(jié)點(diǎn)接在頭節(jié)點(diǎn)的后面。
addWaiter(Node.EXCLUSIVE)方法最終返回了代表了當(dāng)前線程的Node節(jié)點(diǎn),在返回的那一刻,這個(gè)節(jié)點(diǎn)必然是當(dāng)時(shí)的sync queue的尾節(jié)點(diǎn)。
不過(guò)值得注意的是,enq方法也是有返回值(雖然這里我們并沒(méi)有使用它的返回值),但是它返回的是node節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn),這個(gè)返回值雖然在addWaiter方法中并沒(méi)有使用,但是在其他地方會(huì)被用到。
我們?cè)倩氐将@取鎖的邏輯中:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
當(dāng)addWaiter(Node.EXCLUSIVE)執(zhí)行完畢后,節(jié)點(diǎn)現(xiàn)在已經(jīng)被成功添加到sync queue中了,接下來(lái)將執(zhí)行acquireQueued方法。
acquireQueued該方法是最復(fù)雜的一個(gè)方法, 也是最難啃的骨頭, 看代碼之前首先簡(jiǎn)單的說(shuō)明幾點(diǎn):
(1) 能執(zhí)行到該方法, 說(shuō)明addWaiter 方法已經(jīng)成功將包裝了當(dāng)前Thread的節(jié)點(diǎn)添加到了等待隊(duì)列的隊(duì)尾
(2) 該方法中將再次嘗試去獲取鎖
(3) 在再次嘗試獲取鎖失敗后, 判斷是否需要把當(dāng)前線程掛起
為什么前面獲取鎖失敗了, 這里還要再次嘗試獲取鎖呢?
首先, 這里再次嘗試獲取鎖是基于一定的條件的,即:
當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)就是HEAD節(jié)點(diǎn)
因?yàn)槲覀冎溃琱ead節(jié)點(diǎn)就是個(gè)啞節(jié)點(diǎn),它不代表任何線程,或者代表了持有鎖的線程,如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)就是head節(jié)點(diǎn),那就說(shuō)明當(dāng)前節(jié)點(diǎn)已經(jīng)是排在整個(gè)等待隊(duì)列最前面的了。
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // 在當(dāng)前節(jié)點(diǎn)的前驅(qū)就是HEAD節(jié)點(diǎn)時(shí), 再次嘗試獲取鎖 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } //在獲取鎖失敗后, 判斷是否需要把當(dāng)前線程掛起 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
注意,這里又來(lái)了個(gè)自旋操作,我們一段段來(lái)看:
final Node p = node.predecessor(); // 在當(dāng)前節(jié)點(diǎn)的前驅(qū)就是HEAD節(jié)點(diǎn)時(shí), 再次嘗試獲取鎖 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; }
首先我們獲取尾節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)(因?yàn)樯弦徊街蟹祷氐木褪俏补?jié)點(diǎn),并且這個(gè)節(jié)點(diǎn)就是代表了當(dāng)前線程的Node)。
如果前驅(qū)節(jié)點(diǎn)就是head節(jié)點(diǎn),那說(shuō)明當(dāng)前線程已經(jīng)排在了隊(duì)列的最前面,所以這里我們?cè)僭囍カ@取鎖。如果這一次獲取成功了,即tryAcquire方法返回了true, 則我們將進(jìn)入if代碼塊,調(diào)用setHead方法:
private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }
這個(gè)方法將head指向傳進(jìn)來(lái)的node,并且將node的thread和prev屬性置為null, 如下圖所示:
可以看出,這個(gè)方法的本質(zhì)是丟棄原來(lái)的head,將head指向已經(jīng)獲得了鎖的node。但是接著又將該node的thread屬性置為null了,這某種意義上導(dǎo)致了這個(gè)新的head節(jié)點(diǎn)又成為了一個(gè)啞節(jié)點(diǎn),它不代表任何線程。為什么要這樣做呢,因?yàn)樵趖ryAcquire調(diào)用成功后,exclusiveOwnerThread屬性就已經(jīng)記錄了當(dāng)前獲取鎖的線程了,此處沒(méi)有必要再記錄。這某種程度上就是將當(dāng)前線程從等待隊(duì)列里面拿出來(lái)了,是一個(gè)變相的出隊(duì)操作。
還有另外一個(gè)特點(diǎn)是,這個(gè)setHead方法只是個(gè)普通方法,并沒(méi)有像之前enq方法中那樣采用compareAndSetHead方法,這是為什么呢? 同我們之前分析setState方法一樣:
因?yàn)檫@里不會(huì)產(chǎn)生競(jìng)爭(zhēng)!
在enq方法中,當(dāng)我們?cè)O(shè)置頭節(jié)點(diǎn)的時(shí)候,是新建一個(gè)啞節(jié)點(diǎn)并將它作為頭節(jié)點(diǎn),這個(gè)時(shí)候,可能多個(gè)線程都在執(zhí)行這一步,因此我們需要通過(guò)CAS操作保證只有一個(gè)線程能成功。
在acquireQueued方法里,由于我們?cè)谡{(diào)用到setHead的時(shí),已經(jīng)通過(guò)tryAcquire方法獲得了鎖,這意味著:
此時(shí)沒(méi)有其他線程在創(chuàng)建新的頭節(jié)點(diǎn)——因?yàn)楹苊黠@此時(shí)隊(duì)列并不是空的,不會(huì)執(zhí)行到創(chuàng)建頭節(jié)點(diǎn)的代碼
此時(shí)能執(zhí)行setHead的只有一個(gè)線程——因?yàn)橐獔?zhí)行到setHead, 必然是tryAcquire已經(jīng)返回了true, 而同一時(shí)刻,只有一個(gè)線程能獲取到鎖
綜上,在整個(gè)if語(yǔ)句內(nèi)的代碼即使不加鎖,也是線程安全的,不需要采用CAS操作。
接下來(lái)我們?cè)賮?lái)看看另一種情況,即p == head && tryAcquire(arg)返回了false,此時(shí)我們需要判斷是否需要將當(dāng)前線程掛起:
shouldParkAfterFailedAcquire從函數(shù)名也可以看出, 該方法用于決定在獲取鎖失敗后, 是否將線程掛起.
決定的依據(jù)就是前驅(qū)節(jié)點(diǎn)的waitStatus值。
(有沒(méi)發(fā)現(xiàn)一直到現(xiàn)在,前面的分析中我們都沒(méi)有用到waitStatus的值,終于在這里要用到了)
我們先來(lái)回顧一下waitStatus有哪些狀態(tài)值:
static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3;
一共有四種狀態(tài),但是我們?cè)陂_(kāi)篇的時(shí)候就說(shuō)過(guò),在獨(dú)占鎖鎖的獲取操作中,我們只用到了其中的兩個(gè)——CANCELLED和SIGNAL。
當(dāng)然,前面我們?cè)趧?chuàng)建節(jié)點(diǎn)的時(shí)候并沒(méi)有給waitStatus賦值,因此每一個(gè)節(jié)點(diǎn)最開(kāi)始的時(shí)候waitStatus的值都被初始化為0,即不屬于上面任何一種狀態(tài)。
那么CANCELLED和SIGNAL代表什么意思呢?
CANCELLED狀態(tài)很好理解,它表示Node所代表的當(dāng)前線程已經(jīng)取消了排隊(duì),即放棄獲取鎖了。
SIGNAL這個(gè)狀態(tài)就有點(diǎn)意思了,它不是表征當(dāng)前節(jié)點(diǎn)的狀態(tài),而是當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)的狀態(tài)。
當(dāng)一個(gè)節(jié)點(diǎn)的waitStatus被置為SIGNAL,就說(shuō)明它的下一個(gè)節(jié)點(diǎn)(即它的后繼節(jié)點(diǎn))已經(jīng)被掛起了(或者馬上就要被掛起了),因此在當(dāng)前節(jié)點(diǎn)釋放了鎖或者放棄獲取鎖時(shí),如果它的waitStatus屬性為SIGNAL,它還要完成一個(gè)額外的操作——喚醒它的后繼節(jié)點(diǎn)。
有意思的是,SIGNAL這個(gè)狀態(tài)的設(shè)置常常不是節(jié)點(diǎn)自己給自己設(shè)的,而是后繼節(jié)點(diǎn)設(shè)置的,這里給大家打個(gè)比方:
比如說(shuō)出去吃飯,在人多的時(shí)候經(jīng)常要排隊(duì)取號(hào),你取到了8號(hào),前面還有7個(gè)人在等著進(jìn)去,你就和排在你前面的7號(hào)講“哥們,我現(xiàn)在排在你后面,隊(duì)伍這么長(zhǎng),估計(jì)一時(shí)半會(huì)兒也輪不到我,我去那邊打個(gè)盹,一會(huì)輪到你進(jìn)去了(release)或者你不想等了(cancel), 麻煩你都叫醒我”,說(shuō)完,你就把他的waitStatus值設(shè)成了SIGNAL。
換個(gè)角度講,當(dāng)我們決定要將一個(gè)線程掛起之前,首先要確保自己的前驅(qū)節(jié)點(diǎn)的waitStatus為SIGNAL,這就相當(dāng)于給自己設(shè)一個(gè)鬧鐘再去睡,這個(gè)鬧鐘會(huì)在恰當(dāng)?shù)臅r(shí)候叫醒自己,否則,如果一直沒(méi)有人來(lái)叫醒自己,自己可能就一直睡到天荒地老了。
理解了CANCELLED和SIGNAL這兩個(gè)狀態(tài)的含義后,我們?cè)賮?lái)看看shouldParkAfterFailedAcquire是怎么用的:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; // 獲得前驅(qū)節(jié)點(diǎn)的ws if (ws == Node.SIGNAL) // 前驅(qū)節(jié)點(diǎn)的狀態(tài)已經(jīng)是SIGNAL了,說(shuō)明鬧鐘已經(jīng)設(shè)了,可以直接睡了 return true; if (ws > 0) { // 當(dāng)前節(jié)點(diǎn)的 ws > 0, 則為 Node.CANCELLED 說(shuō)明前驅(qū)節(jié)點(diǎn)已經(jīng)取消了等待鎖(由于超時(shí)或者中斷等原因) // 既然前驅(qū)節(jié)點(diǎn)不等了, 那就繼續(xù)往前找, 直到找到一個(gè)還在等待鎖的節(jié)點(diǎn) // 然后我們跨過(guò)這些不等待鎖的節(jié)點(diǎn), 直接排在等待鎖的節(jié)點(diǎn)的后面 (是不是很開(kāi)心!!!) do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 前驅(qū)節(jié)點(diǎn)的狀態(tài)既不是SIGNAL,也不是CANCELLED // 用CAS設(shè)置前驅(qū)節(jié)點(diǎn)的ws為 Node.SIGNAL,給自己定一個(gè)鬧鐘 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
可以看出,shouldParkAfterFailedAcquire所做的事情無(wú)外乎:
如果為前驅(qū)節(jié)點(diǎn)的waitStatus值為 Node.SIGNAL 則直接返回 true
如果為前驅(qū)節(jié)點(diǎn)的waitStatus值為 Node.CANCELLED (ws > 0), 則跳過(guò)那些節(jié)點(diǎn), 重新尋找正常等待中的前驅(qū)節(jié)點(diǎn),然后排在它后面,返回false
其他情況, 將前驅(qū)節(jié)點(diǎn)的狀態(tài)改為 Node.SIGNAL, 返回false
注意了,這個(gè)函數(shù)只有在當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的waitStatus狀態(tài)本身就是SIGNAL的時(shí)候才會(huì)返回true, 其他時(shí)候都會(huì)返回false, 我們?cè)倩氐竭@個(gè)方法的調(diào)用處:
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; } // 我們?cè)谶@里!在這里!!在這里!!! // 我們?cè)谶@里!在這里!!在這里!!! // 我們?cè)谶@里!在這里!!在這里!!! if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
可以看出,當(dāng)shouldParkAfterFailedAcquire返回false后,會(huì)繼續(xù)回到循環(huán)中再次嘗試獲取鎖——這是因?yàn)榇藭r(shí)我們的前驅(qū)節(jié)點(diǎn)可能已經(jīng)變了(搞不好前驅(qū)節(jié)點(diǎn)就變成head節(jié)點(diǎn)了呢)。
當(dāng)shouldParkAfterFailedAcquire返回true,即當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的waitStatus狀態(tài)已經(jīng)設(shè)為SIGNAL后,我們就可以安心的將當(dāng)前線程掛起了,此時(shí)我們將調(diào)用parkAndCheckInterrupt:
parkAndCheckInterrupt到這個(gè)函數(shù)已經(jīng)是最后一步了, 就是將線程掛起, 等待被喚醒
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); // 線程被掛起,停在這里不再往下執(zhí)行了 return Thread.interrupted(); }
注意!LockSupport.park(this)執(zhí)行完成后線程就被掛起了,除非其他線程unpark了當(dāng)前線程,或者當(dāng)前線程被中斷了,否則代碼是不會(huì)再往下執(zhí)行的,后面的Thread.interrupted()也不會(huì)被執(zhí)行,那后面這個(gè)Thread.interrupted()是干什么用的呢? 我們下一篇再講。
總結(jié)AQS中用state屬性表示鎖,如果能成功將state屬性通過(guò)CAS操作從0設(shè)置成1即獲取了鎖
獲取了鎖的線程才能將exclusiveOwnerThread設(shè)置成自己
addWaiter負(fù)責(zé)將當(dāng)前等待鎖的線程包裝成Node,并成功地添加到隊(duì)列的末尾,這一點(diǎn)是由它調(diào)用的enq方法保證的,enq方法同時(shí)還負(fù)責(zé)在隊(duì)列為空時(shí)初始化隊(duì)列。
acquireQueued方法用于在Node成功入隊(duì)后,繼續(xù)嘗試獲取鎖(取決于Node的前驅(qū)節(jié)點(diǎn)是不是head),或者將線程掛起
shouldParkAfterFailedAcquire方法用于保證當(dāng)前線程的前驅(qū)節(jié)點(diǎn)的waitStatus屬性值為SIGNAL,從而保證了自己掛起后,前驅(qū)節(jié)點(diǎn)會(huì)負(fù)責(zé)在合適的時(shí)候喚醒自己。
parkAndCheckInterrupt方法用于掛起當(dāng)前線程,并檢查中斷狀態(tài)。
如果最終成功獲取了鎖,線程會(huì)從lock()方法返回,繼續(xù)往下執(zhí)行;否則,線程會(huì)阻塞等待。
(完)
下一篇: 逐行分析AQS源碼(2)——獨(dú)占鎖的釋放
查看更多系列文章:系列文章目錄
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/76458.html
摘要:我們知道,這個(gè)函數(shù)將返回當(dāng)前正在執(zhí)行的線程的中斷狀態(tài),并清除它。注意,中斷對(duì)線程來(lái)說(shuō)只是一個(gè)建議,一個(gè)線程被中斷只是其中斷狀態(tài)被設(shè)為線程可以選擇忽略這個(gè)中斷,中斷一個(gè)線程并不會(huì)影響線程的執(zhí)行。 前言 系列文章目錄 上一篇文章 我們逐行分析了獨(dú)占鎖的獲取操作, 本篇文章我們來(lái)看看獨(dú)占鎖的釋放。如果前面的鎖的獲取流程你已經(jīng)趟過(guò)一遍了, 那鎖的釋放部分就很簡(jiǎn)單了, 這篇文章我們直接開(kāi)始看...
摘要:為了避免一篇文章的篇幅過(guò)長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開(kāi)始寫(xiě)博客的,寫(xiě)作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷?xiě)作的時(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問(wèn)題,就要把一系列知識(shí)都了解一遍,寫(xiě)出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...
摘要:為了避免一篇文章的篇幅過(guò)長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開(kāi)始寫(xiě)博客的,寫(xiě)作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷?xiě)作的時(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問(wèn)題,就要把一系列知識(shí)都了解一遍,寫(xiě)出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...
摘要:而對(duì)于共享鎖而言,由于鎖是可以被共享的,因此它可以被多個(gè)線程同時(shí)持有。換句話說(shuō),如果一個(gè)線程成功獲取了共享鎖,那么其他等待在這個(gè)共享鎖上的線程就也可以嘗試去獲取鎖,并且極有可能獲取成功。 前言 前面兩篇我們以ReentrantLock為例了解了AQS獨(dú)占鎖的獲取與釋放,本篇我們來(lái)看看共享鎖。由于AQS對(duì)于共享鎖與獨(dú)占鎖的實(shí)現(xiàn)框架比較類似,因此如果你搞定了前面的獨(dú)占鎖模式,則共享鎖也就很...
摘要:相較于方法,提供了超時(shí)等待機(jī)制注意,在方法中,我們用到了的返回值,如果該方法因?yàn)槌瑫r(shí)而退出時(shí),則將返回。的這個(gè)返回值有助于我們理解該方法究竟是因?yàn)楂@取到了鎖而返回,還是因?yàn)槌瑫r(shí)時(shí)間到了而返回。 前言 系列文章目錄 CountDownLatch是一個(gè)很有用的工具,latch是門(mén)閂的意思,該工具是為了解決某些操作只能在一組操作全部執(zhí)行完成后才能執(zhí)行的情景。例如,小組早上開(kāi)會(huì),只有等所有人...
閱讀 702·2021-09-29 09:34
閱讀 2554·2019-08-30 15:53
閱讀 3361·2019-08-29 17:17
閱讀 761·2019-08-29 16:08
閱讀 1120·2019-08-29 13:03
閱讀 951·2019-08-27 10:54
閱讀 688·2019-08-26 13:39
閱讀 2859·2019-08-26 13:34