走近AbstractQueuedSynchronizer


走近AbstractQueuedSynchronizer

一、從類結構開始

Java並發包中的同步器是很多並發組件的基礎,如各種Lock,ConcurrentHashMap中的Segment,阻塞隊列,CountDownLatch等。按我們一貫的風格,讓我們直接走近設計者對其的詮釋。
在java.util.concurrent.locks包中, AbstractQueuedSynchronizer直接繼承自AbstractOwnableSynchronizer,在接下來的文字中有時會簡寫為AQS.
1. AbstractOwnableSynchronizer
AbstractOwnableSynchronizer是一種可由一個線程獨占的同步器,同時也是創建鎖的基礎,它還包含了一種叫所有權的概念。AbstractOwnableSynchronizer本身不管理內部數據,但是它的子類可以用來維護一些值並用於控制或監視線程的訪問。
AbstractOwnableSynchronizer內部只有一個屬性:獨占當前同步狀態的線程和該屬性的set/get方法。
代碼如下:

public abstract class AbstractOwnableSynchronizer { private transient Thread exclusiveOwnerThread; } 


2. AbstractQueuedSynchronizer
AbstractQueuedSynchronizer提供了一種框架,用於實現阻塞鎖和其他基於先入先出(FIFO)等待隊列的同步組件。該類用一個Int類型的原子變量來表示一種狀態。子類必須實現該類的protect方法,以此來改變同步狀態。在獲取或釋放該狀態時,需要定義這個狀態值。類中的其他方法實現了線程入隊與阻塞功能,子類依然可以維護其他狀態字段,但是只能使用getState、setState、compareAndSetState方法來跟蹤同步狀態。
子類應該定義為非公共的內部工具類,並需要在類中實現相應的同步方法。AbstractQueuedSynchronizer本身沒有實現任何接口,支持獨占式與共享式的獲取同步狀態。如果是獨占模式,那么其他線程則不能獲取到,而共享式則允許多個線程同時獲取。兩種不同模式下的等待線程共享同一個隊列,通常實現的子類只支持一種模式,但是也有兩種都支持的,如ReadWriteLock。僅支持獨占或共享的子類可以不用實現對應模式所定義的方法。
AbstractQueuedSynchronizer類中定義了一個嵌套類ConditionObject。ConditionObject主要提供一種條件,由子類決定是否支持獨占模式,並由isHeldExclusively方法決定當前線程是否是獨占的獲取同步狀態。
除此,類中還定義了一些方法,用於檢查、監控內部隊列與條件對象。

 

二、隊列節點

正式走近AbstractQueuedSynchronizer。
在AbstractQueuedSynchronizer內部,有一個靜態的Node內部類,Doug對他解釋如下:

等待隊列節點
等待隊列是一種“CLH(自旋鎖)”鎖隊列。我們用自旋鎖來實現阻塞同步器,但用的是同樣的策略來控制一個線程的前驅節點的信息。每個節點中的status字段記錄了一個線程是否已阻塞。當一個節點的前驅節點釋放鎖后會以信號的形式通知該節點,隊列的每個結點作為一個特定通知風格(specific-notification-style)的監視器服務,會持有一個單獨的等待線程,但是status字段不會控制是否線程能被賦予鎖。如果一個線程是第一個進入隊列的節點,他就可以嘗試獲取鎖,但是也不能保證獲取成功,只是有了競爭的權利。所以當前釋放鎖的競爭者線程可能需要再次等待。
為了進入CLH鎖隊列,你只需要原子的拼接成一個尾節點。要出隊列的話,你僅需要設置head字段即可。

        +------+ prev +-----+     +-----+
   head |      | <---- |      | <---- |     |  tail
        +------+     +-----+      +-----+

插入節點到CLH隊列要求在tail節點上是原子性的操作,未到隊列的節點與入隊的節點之間的界定就是是否有一個簡單的原子指向操作執行該節點。類似的,節點出隊牽涉到操作的就是更新head節點。然而,對於節點來說卻需要花很多功夫來決定他們的后繼結點是什么,處理一部分因超時或中斷而導致的取消。

prev鏈向符主要是用於處理取消,如果一個節點被取消后,他的后繼節點可以重新鏈向一個有效的前驅節點。(想要了解自旋鎖的更多說明可參考Scott and Scherer的論文
我們還使用了next鏈向符,用於實現阻塞的原理。每個節點里保留了一個線程的Id,因此一個前驅節點可以通過遍歷next節點來找到具體的線程然后喚醒next節點。決定后繼節點時需要避免與新入隊的節點競爭去設置他們前驅節點的next字段。
取消節點采用一些保守的算法。由於我們必需要根據某節點來輪詢取消,因此可能會錯過在之前或之后的節點。在執行取消時,會喚醒他的后繼節點,並允許他們穩定在一個新的前驅節點上。
CLH隊列需要一個虛擬的head節點來開始,但不會在構造方法中創建他,因為如果沒有競爭那么會很浪費。相反,在創建節點時遇到第一次競爭時才會設置head和tail節點。

等待線程使用的也是同樣的節點,只不過用的是額外的鏈向符。條件是用來鏈接隊列的,線程在等待時,就會新增一個節點到條件隊列中,再被得到通知時,該節點就轉入到主隊列中。節點用一個特殊的狀態值來表示在哪個隊列中。

三、節點狀態

類上的注釋說完了,開始說說類本身吧。從Node開始。

static final class Node { //靜態內部Final類 //標記符,表示在共享模式下的等待節點 static final Node SHARED = new Node(); //標記符,表示在獨占模式下的等待節點 static final Node EXCLUSIVE = null; //等待狀態值,表示一個線程已經被取消了 static final int CANCELLED = 1; //等待狀態值,表示一個后繼節點的線程需要喚醒 static final int SIGNAL = -1; //等待狀態值,表示線程等待在某種條件下 static final int CONDITION = -2; //等待狀態值,表示下一次共享式獲取狀態的操作應該無條件的傳播 static final int PROPAGATE = -3; /*** 狀態字段,取值如下: SIGNAL: 當前結點的后繼節點將會是阻塞的(通過park方法),因此當前結點需要喚醒他的后繼節點,當他釋放或取消后。為了避免競爭,獲取同步狀態的方法必須搶先表示自己需要信號,然后重新原子的獲取。最后可能是獲取失敗,或者再次被阻塞。 CANCELLED: 由於超時、中斷等原因,當前結點會被取消。取消后,節點不會釋放狀態。特殊情景下,被取消的節點中的線程將不會再被阻塞 CONDITION: 當前結點在一個條件隊列中,再被轉移之前將不會被作為同步節點。被轉移時該值會被設置為0。 PROPAGATE: 共享式方式釋放同步狀態后應該被傳播到其他節點。這種設置(僅對head節點)在doReleaseShared方法中確保了可以持續,及時有其他的干預操作。 0: 非以上狀態 **/ volatile int waitStatus; /** 當前節點(線程)依賴於等待的狀態值而鏈向的前驅節點。在入隊列時被賦值,在出隊時被置空(讓GC回收)。 */ volatile Node prev; /** 當前結點(線程)在釋放同步狀態后會喚醒的后繼節點 */ volatile Node next; //節點關聯的線程,構造時被初始化、用完后置空 volatile Thread thread; /** 鏈向的下一個等待節點,或是一個特殊值SHARED.由於只能是獨占式的訪問條件隊列,所以只需簡單的鏈向隊列就行了。又由於條件只能是獨占式的獲取,我們保留了一個字段並使用特殊的值來表示共享模式。 **/ Node nextWaiter; //如果節點是以共享模式在等待,則返回true final boolean isShared() {return nextWaiter == SHARED;} 一組構造方法 Node (){} Node(Thread thread, Node mode) { // 添加一個等待節點時,可使用 this.nextWaiter = mode; this.thread = thread; } //添加一個依賴於某條件的節點時,可使用 Node(Thread thread, int waitStatus) { this.waitStatus = waitStatus; this.thread = thread; } } 

 

四、類成員與幾個方法

AbstractQueuedSynchronizer定義的重要的屬性和方法如下:

//等待隊列中的頭節點,會延遲初始化。只能通過setHead方法修改頭節點。注意:如果頭節點存在,需要保證他的waitStatus不能是CANCELLED。
private transient volatile Node head; //等待隊列中的尾節點,會延遲初始化。只能通過enq方法來添加一個新的等待節點。 private transient volatile Node tail; //同步狀態 private volatile int state; //返回當前的同步狀態值。該操作擁有volatile讀的內存語義。 protected final int getState() { return state; } //設置同步狀態的值 protected final void setState(int newState) { state = newState; } //如果當前的狀態值等於預期的值,則原子的設置同步狀態值為給定的update值,設置成功后返回true.如果實際的值不等於預期的expect值,則返回false protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } 

 

看看一些隊列操作的工具方法。

/**
新增節點到隊列中。 **/ private Node enq(final Node node) { for (;;) { Node t = tail; //先驗證尾節點是否為空 if (t == null) { // 如果為空,則必須初始化 if (compareAndSetHead(new Node())) tail = head; //第一次入隊時,頭節點和尾節點相同 } else { node.prev = t; if (compareAndSetTail(t, node)) { //原子的設置尾節點為當前結點,並鏈接好前后節點 t.next = node; return t; } } } } /*** 用當前的線程和給定的模式來創建一個節點,並加入到隊列中。 mode為Node.EXCLUSIVE表示獨占式,為Node.SHARED表示共享式 **/ private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // 嘗試快速入隊,如果失敗則候補到全隊列中 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; } /** 設置隊列中的head節點為給的的node,從而出隊列。僅會被acquire方法調用。 */ private void setHead(Node node) { head = node; node.thread = null; node.prev = null; } /*** 如果存在后繼節點,則喚醒該節點 **/ private void unparkSuccessor(Node node) { /*  如果狀態是負數(表示需要一個信號),先搶先設置狀態為0,表示自己需要信號。  當然也可能獲取失敗,然后則進入等待隊列  */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /*  正常情況下,直接喚醒后繼節點。但是后繼節點是空的或被取消了,則從尾節點開始遍歷出一個沒有被取消的節點  */ 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); } 


代碼先看到這里…

 

五、同步器原理

同步器依賴內部的同步隊列(FIFO)來完成同步狀態的管理,當前線程獲取同步狀態失敗時,同步器會將當前線程以及等待狀態等信息構造成一個節點並將其加入到同步隊列,同時阻塞當前線程。首節點是獲取同步狀態成功的節點,同步狀態釋放時,首節點會喚醒后繼節點中的線程,並可能讓其獲取同步狀態。
同步器擁有的頭節點(head)、尾節點(tail), 還有一個線程隊列,以雙向鏈表的形式體現。

同步隊列的基本結構
線程在嘗試獲取同步狀態時,AQS會把該線程構造成一個節點,節點結構即上文中的Node類,然后讓內部的tail節點引用該節點,此階段是通過compareAndSetTail方法利用CAS原理設置tail指向該節點的引用。

獲取到同步的線程,AQS中的head節點會指向包含該線程的節點,執行完相應的邏輯后,會釋放同步狀態。然后首節點會喚醒它的后繼節點(next引用)並讓該節點中的線程參與獲取同步狀態的活動。

六、獨占式獲取與釋放同步狀態

方法說明如下:
當前線程獨占的獲取同步狀態,即當前線程調用compareAndSetState方法設置內部的state變量值為特點的值,這由AQS具體子類在tryAcquire方法中指定值。compareAndSetState方法通過CAS設置成功后會返回true, 代表該線程設置狀態成功,也就意味着獲取同步狀態成功。最后設置當前的exclusiveOwnerThread屬性為當前線程。(AQS繼承AbstractOwnableSynchronizer)

public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 


由於子類的具體實現,因此獲取同步狀態有不同的規則,如公平的獲取,非公平的獲取等。

 

如果tryAcquire方法返回false, 意味着當前線程獲取同步狀態失敗,則執行acquireQueued方法。

先看addWaiter方法
獨占式獲取,參數為:Node.EXCLUSIVE

private Node addWaiter(Node mode) { //按類型構造一個節點 Node node = new Node(Thread.currentThread(), mode); //快速加到尾節點,失敗了則重新入隊 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; } ``` 再看看accuireQueued()方法 方法主要意圖為當前線程不間斷的一直去獲取同步狀態沒有獲取到則進入自旋過程```java 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); } } 


在自旋的過程里,如果當前節點的prev節點的等待狀態為SIGNAL后,則會park線程了,因為節點的前驅節點不是頭節點,所以讓自己先等待。

 

直到有節點獲取到后,之后會釋放,直到讓所有的節點都獲取到。
釋放的方法如下:
釋放同步狀態,由子類來完成,一般思路是更改state為0或設置state自減arg。
然后喚醒后繼節點,讓其參與獲取同步狀態。

public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } 

 

七、共享式獲取與釋放

共享式與獨占式獲取最主要的區別在於同一時刻能否有多個線程同時獲取到同步狀態。如圖所示:

共享式獲取同步狀態的方法是帶有share標識的。

public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } 


acquireShared方法表示以共享的方式來獲取同步狀態,同時可以忽略線程是否中斷。
tryAcquireShared方法與tryAccquird類似,也是由子類的行為來決定。返回小於0 (通常為-1)的數代表獲取失敗,返回大於等於0代表獲取到同步狀態。沒有獲取到則進入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) { 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,意為讓自己的后繼節點也去獲取同步狀態。
在setHeadAndPropagate方法中會設置自己為頭節點,最后會調用doReleaseShared釋放同步狀態。

 

看看doReleaseShared方法,邏輯為更改waitStatus為Node.PROPAGATE,意為傳播該行為 ,並喚醒后繼節點。

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 unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } ``` ## AQS具體應用 在JUC包中AQS的應用很多如ThreadPoolExecutor中的Worker阻塞隊列中poll或take方法還有重入鎖CountDownLatchConcurrentHashMap還有讀寫鎖等下面舉其中一部分例子**1. 重入鎖** 重入鎖ReentrantLock代表一個線程可以對資源重復加鎖Java中的synchronized關鍵字是隱式的支持重進入而重入鎖在代碼層面實現了重入的含義線程在執行一個遞歸方法時獲取了鎖后仍能連續多次的獲得該鎖而有些排它鎖則只能獲取一次下一次獲取則只能重新排隊競爭如你所想重入鎖同時是一把非公平鎖可直接看看ReentrantLock的代碼實現不知還曾記否Doug對AQS的使用建議子類應該定義為非公共的內部工具類並需要在類中實現相應的同步方法可以看到所以同步組件的實現都是定了一個Sync的內部類ReentrantLock實現重新獲取鎖的邏輯如下(直接看非公平鎖的實現): ```java static final class NonfairSync extends Sync { /**  獲取鎖時,先闖入獲取。沒有獲取到再到候補隊列中  */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } 


在Sync中定義的nonfairTryAcquire方法解決了線程再次獲取鎖:如果再次獲取鎖的線程是當前線程,則對當前的同步狀態執行累加acquires值。

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; } 


而在釋放鎖時,需要保證鎖的最終釋放,需要對同步狀態的獲取次數自減,當計數為0時表示鎖已經成功釋放。

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; } 


2. CountDownLatch
Latch意為門閂,門鎖,CountDownLatch中文直意為計數門閂,用於在線程間計數后最后放開門閂。
CountDownLatch的文檔描述為:用於輔助一個或多個線程等待一系列的操作執行完成后然后才可以執行后續動作。CountDownLatch是典型的共享模式的同步組件。初始化時,設置當前的線程數量為同步狀態的值,每次執行countdown方法時,同步狀態自減。直到同步狀態為0時,代表以及全部釋放,然后發起調用的線程就可以執行后續動作。看看CountDownLatch是怎么借用AQS來完成countDown和openLatch的。
內部直接使用一個靜態工具類繼承AQS,然后在構造方法設置同步狀態為構造參數。
Sync(int count) {
setState(count);
}
CountDownLatch表示當前線程(或調用線程)需要等待其他線程完成其相應的工作,因此設置的鎖計數器是針對其他線程的,因此當前線程想要執行后續操作,需要等待其他線程都釋放鎖,直到同步狀態為0時,當前線程才會從等待中返回。
因此CountDownLatch的核心方法為await(),countDown()。
重寫的tryAcquireShared方法為

protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } 

 

而await方法實現為:表示在等待時是響應中的。

public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } 


acquireSharedInterruptibly()是AQS中的方法,該方法會調用子類的tryAcquireShared方法來驗證同步狀態是否為0.

 

而在countDown方法則含義簡明,直接釋放一個狀態計數。

public void countDown() { sync.releaseShared(1); } protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } } 


注: AQS中的獲取與釋放方法前文中都已經闡述。
3. ConcurrentHashMap
我覺得ConcurrentHashMap的結構就是兩個HashMap。添加元素時,先根據key找到對應的段(Segment),再根據段找到具體的Bucket桶的位置(數組的索引),最后添加元素到Entry中。當然這段描述簡化了很多,接下來我們來探索一下ConcurrentHashMap的並發控制機制。
ConcurrentHashMap把欲存儲的數據分成了多個段(Segment), 而每個段里就類似一個HashMap, 里面包含一個Table數組,並用於存儲數據。
因此,ConcurrentHashMap對於並發的控制轉交給了內部的Segment,而Segment自身是繼承了ReentrantLock重入鎖(注:在JDK7中)。所以,最終的並發控制還是由AQS來接管的,所以又不得不聊聊ConcurrentHashMap和AQS是怎么哥倆好的。
3.1 初始化
與HashMap類似,ConcurrentHashMap也是先設置一些基礎屬性。如指定並發級別(就是指定最大Segment數量),負載因子,容量設置為2的n次方,以及用於hash的掩碼和移位數值,最后構造Segment數組。
3.2 put元素(挖坑埋寶)
put元素時,會先找到key映射在那個Segment中,最后由具體的Segment來完成put操作。

public V put(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); int hash = hash(key); int j = (hash >>> segmentShift) & segmentMask; if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment s = ensureSegment(j); return s.put(key, hash, value, false); } 


在Segment put元素時,會對當前Segment加鎖,如代碼:

final V put(K key, int hash, V value, boolean onlyIfAbsent) { HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value); 省略} 


如果當前線程加鎖成功,則node為null. 然后就想HashMap中添加元素那樣,執行添加元素。
如果tryLock返回false, 代表當前線程獲取鎖失敗,說明正在有其他線程在操作同一個Segment。於是,便執行scanAndLockForPut方法。
scanAndLockForPut很有意思。某一個線程在同一個Segment上執行put方法,發現有其他線程已經獲取到了鎖,因此不讓它put。但是咱不能閑着啊,不能執行put,咱能提前干點其他的事兒啊,比如先算算給我的key應該落在哪個坑里(entryForHash方法),等我找到我的坑后,再去看看其他線程活兒干完沒有, 如果你還沒有put完成,那我先把要埋的寶貝准備好(構造節點),只要等你釋放鎖后,我便立即埋坑。

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) { //找坑 HashEntry<K,V> first = entryForHash(this, hash); HashEntry<K,V> e = first; HashEntry<K,V> node = null; int retries = -1; // negative while locating node while (!tryLock()) { //再次去獲取鎖 HashEntry<K,V> f; // to recheck first below //雙重檢測、以防止這個坑別人填了 if (retries < 0) { if (e == null) { if (node == null) // speculatively create node //准備好土 node = new HashEntry<K,V>(hash, key, value, null); retries = 0; } else if (key.equals(e.key)) retries = 0; else e = e.next; } else if (++retries > MAX_SCAN_RETRIES) { lock(); break; } else if ((retries & 1) == 0 && (f = entryForHash(this, hash)) != first) { e = first = f; // re-traverse if entry changed retries = -1; } } return node; //最后把准備好的寶貝返回給put方法,用於正式埋坑。 } ```java 整體上看下來ConcurrentHashMap的put方法就像是一個挖坑埋寶的故事3.3 get元素挖寶最難的挖坑需要先去找哪里可以挖然后如果你要在同一片土地上埋寶的話咱還等排隊)。 但是挖寶貝咱就管不了那么多了先找到那個坑看誰挖的快看誰挖的深淺就行對應到get方法就是直接找到那個Segment然后再找到具體的索引然后逐個驗證key,最后取走就ok. ```java public V get(Object key) { Segment<K,V> s; // manually integrate access methods to reduce overhead HashEntry<K,V>[] tab; int h = hash(key); //找Segment long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null && (tab = s.table) != null) { //在Segment中找具體的數組索引 for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); e != null; e = e.next) { K k; if ((k = e.key) == key || (e.hash == h && key.equals(k))) return e.value; } } return null; //沒有找到(哥,您當時可能沒有埋寶,您想開點)。 } 

 

九、結束語

AQS包含的設計思想是非一時半刻就能吸收的,還有很多哲學上的啟發。因此需要花很多時間來分析和付諸很多的思考與其中。本文講述的內容恐怕浮光掠影,比如還有沒有探討的內容有Conditon接口、LockSupport工具。但一言以蔽之,Conditon及其實現類ConditionObject就是實現了Java中的等待/通知機制,即wait方法和notify方法的語義。而LockSupport是一個工具類,用於阻塞和喚醒當前線程,而其也是依賴於JVM自身來控制的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM