互聯網JAVA面試常問問題(七)- 帶你走入AQS同步器源碼
- 2019 年 10 月 6 日
- 筆記
周末叫醒我的不是忘記關掉的鬧鐘,而是一顆愛學習的紅心~大家周日好,歡迎和小強一起繼續研究互聯網JAVA面試題~
上一篇文章中,小強給大家介紹了可重入鎖的相關知識,通過學習ReentrantLock的源碼能夠看出,它繼承了AbstractQueuedSynchronizer。所以,本文再簡單的的介紹一下AQS的源碼。
AbstractQueuedSynchronizer
隊列式的同步器,AQS定義了一套多執行緒訪問共享資源的同步器框架,許多同步類實現都依賴於它,如常用的ReentrantLock/Semaphore/CountDownLatch。AQS的源碼如下:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { protected AbstractQueuedSynchronizer() { } static final class Node { ........ } private transient volatile Node head; private transient volatile Node tail; private volatile int state; protected final int getState() { return state; } protected final void setState(int newState) { state = newState; } protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }從源碼可以看出,AQS內部使用一個int成員變數表示同步狀態,通過內置的FIFO隊列來完成資源獲取執行緒的排隊工作,多執行緒爭用資源被阻塞時會進入此隊列,其中volatile修飾了以下變數,來保證了多執行緒之間的可見:
1.內部狀態state
2.等待隊列的頭節點head
3.等待隊列的尾節點head。
state的訪問方式有三種:
1.getState()
2.setState()
3.compareAndSetState()
其中FIFO隊列的實現如下,每個節點中,除了存儲了當前執行緒,前後節點的引用以外,還有一個waitStatus變數,用於描述節點當前的狀態(共四種狀態:1.CANCELLED 取消狀態;2.SIGNAL 等待觸髮狀態;3.CONDITION 等待條件狀態;4.PROPAGATE 狀態需要向後傳播 )
static final class Node { static final AbstractQueuedSynchronizer.Node SHARED = new AbstractQueuedSynchronizer.Node(); static final AbstractQueuedSynchronizer.Node EXCLUSIVE = null; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; volatile int waitStatus; volatile AbstractQueuedSynchronizer.Node prev; volatile AbstractQueuedSynchronizer.Node next; volatile Thread thread;AbstractQueuedSynchronizer.Node nextWaiter; final boolean isShared() { return nextWaiter == SHARED; } final AbstractQueuedSynchronizer.Node predecessor() throws NullPointerException { AbstractQueuedSynchronizer.Node p = prev; if (p == null) throw new NullPointerException(); else return p; } }AQS的不同子類爭用共享資源的方式也不同。AQS子類中,只需要實現共享資源state的獲取與釋放方式即可,至於具體執行緒等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實現好了。自定義同步器實現時主要實現以下幾種方法:
isHeldExclusively():該執行緒是否正在獨佔資源。只有用到condition才需要去實現它。 tryAcquire(int):獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。 tryRelease(int):獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。 tryAcquireShared(int):共享方式。嘗試獲取資源。負數表示失;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。 tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點返回true,否則返回false。
在AQS中,有兩種資源共享方式:Exclusive(獨佔,只有一個執行緒能執行,如ReentrantLock)和Share(共享,多個執行緒可同時執行,如Semaphore/CountDownLatch)。接下來主要介紹Exclusive下是如何獲取和釋放鎖的:
鎖獲取acquire(int)
/** * Acquires in exclusive mode, ignoring interrupts. Implemented * by invoking at least once {@link #tryAcquire}, * returning on success. Otherwise the thread is queued, possibly * repeatedly blocking and unblocking, invoking {@link * #tryAcquire} until success. This method can be used * to implement method {@link Lock#lock}. * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquire} but is otherwise uninterpreted and * can represent anything you like. */ public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }所以在獲取鎖的過程中,我們看到了如下步驟
1.tryAcquire()嘗試直接去獲取資源,如果成功則直接返回;
2.addWaiter()將該執行緒加入等待隊列的尾部,並標記為獨佔模式;
3.acquireQueued()使執行緒在等待隊列中獲取資源,一直獲取到資源後才返回。如果在整個等待過程中被中斷過,則返回true,否則返回false。
4.如果執行緒在等待過程中被中斷過,它是不響應的。只是獲取資源後才再進行自我中斷selfInterrupt(),將中斷補上。
鎖釋放release(int)
/** * Releases in exclusive mode. Implemented by unblocking one or * more threads if {@link #tryRelease} returns true. * This method can be used to implement method {@link Lock#unlock}. * * @param arg the release argument. This value is conveyed to * {@link #tryRelease} but is otherwise uninterpreted and * can represent anything you like. * @return the value returned from {@link #tryRelease} */ public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }此方法是獨佔模式下執行緒釋放共享資源的頂層入口。它會釋放指定量的資源,如果徹底釋放了(即state=0),它會喚醒等待隊列里的其他執行緒來獲取資源。它調用tryRelease()來釋放資源。
小結
最後再回顧一下AQS的應用,以及ReentrantLock是如何實現的
ReentrantLock中state初始化為0,表示未鎖定狀態。當一個執行緒lock()時,會調用tryAcquire()獨佔該鎖並將state+1。此後,其他執行緒再tryAcquire()時就會失敗,直到此執行緒unlock()到state=0(即釋放鎖)為止,其它執行緒才有機會獲取該鎖。當然,釋放鎖之前,這個執行緒自己是可以重複獲取此鎖的(state會累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多麼次,這樣才能保證state是能回到零態的。
一般來說,自定義同步器要麼是獨佔方法,要麼是共享方式,他們也只需實現tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種即可。但AQS也支援自定義同步器同時實現獨佔和共享兩種方式,如ReentrantReadWriteLock。