上篇文章講了Java中顯式鎖Lock和ReadWriteLock的概念及簡單使用示例,本篇文章重點來看一下Java中顯式鎖的底層實現。在看這部分代碼時,我任務比較好理解的方式是帶着問題去看源碼。對於顯式鎖,我們可能會有以下問題:
- 問題1:顯式鎖底層是基於CAS實現,細節是怎樣的?
- 問題2:顯式鎖跟synchronized類似,無法獲取鎖時,會阻塞當前線程,如何實現阻塞的?
- 問題3:顯式鎖提供還可以提供tryLock的操作,獲取不到鎖立即返回,如何實現?
- 問題4:顯式鎖在等待時,可以響應中斷,如何實現?
- 問題5:顯式鎖可以提供公平鎖和非公平鎖,如何實現?
- 問6:讀寫鎖底層又是怎麼實現的?
帶着這些問題,我們來看Java顯式鎖的實現源碼。
1. LockSupport
顯式鎖在無法獲取到鎖時,需要阻塞當前線程,synchronized中是依賴於底層操作系統函數實現的,在顯式鎖中必然也要有類似的機制,可以阻塞申請不到鎖的線程。在顯式鎖中是依賴包java.util.concurrent.locks下的LockSupport實現的,它的基本方法有:
public static void park()
public static void parkNanos(long nanos)
public static void parkUntil(long deadline)
public static void unpark(Thread thread)
park使得當前線程放棄CPU,進入等待狀態(WAITING),操作系統不再對它進行調度,什麼時候再調度呢?有其他線程對它調用了unpark,unpark需要指定一個線程,unpark會使之恢復可運行狀態。看個例子:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread (){
public void run(){
LockSupport.park();
System.out.println("exit");
}
};
t.start();
Thread.sleep(1000);
LockSupport.unpark(t);
}
線程t啓動後調用park,會放棄CPU,主線程睡眠1秒鐘後,調用unpark,線程t恢復運行,輸出exit。
park不同於Thread.yield(),yield只是告訴操作系統可以先讓其他線程運行,但自己依然是可運行狀態,而park會放棄調度資格,使線程進入WAITING狀態。
需要說明的是,park是響應中斷的,當有中斷髮生時,park會返回,線程的中斷狀態會被設置。
park有兩個變體:
- parkNanos:可以指定等待的最長時間,參數是相對於當前時間的納秒數。
- parkUntil:可以指定最長等到什麼時候,參數是絕對時間,是相對於紀元時的毫秒數。
當等待超時的時候,它們也會返回。
park方法還有一些變體,可以指定一個對象,表示是因爲該對象進行等待的,以便於調試,通常傳遞的值是this,如下:
public static void park(Object blocker)
public static void parkNanos(Object blocker, long nanos)
public static void parkUntil(Object blocker, long deadline)
LockSupport有一個方法,可以返回一個線程的blocker對象:
public static Object getBlocker(Thread t)
這些park/unpark方法是怎麼實現的呢?與CAS方法一樣,它們也調用了Unsafe類中的對應方法,Unsafe類最終調用了操作系統的API,從程序員的角度,我們可以認爲LockSupport中的這些方法就是基本操作。
2. AbstractQueuedSynchronizer(AQS)
利用CAS和LockSupport提供的基本方法,肯定就可以實現ReentrantLock了。但Java中還有很多其他併發工具,如ReentrantReadWriteLock、Semaphore、CountDownLatch,它們的實現有很多類似的地方,爲了複用代碼,Java提供了一個抽象類AbstractQueuedSynchronizer,我們簡稱爲AQS,它簡化了併發工具的實現。像ReentrantReadWriteLock、Semaphore、CountDownLatch都是基於AQS實現的。
AQS解決了實現同步器時涉及當的大量細節問題,例如獲取同步狀態、FIFO同步隊列等。基於AQS來構建同步器可以帶來很多好處,它不僅能夠極大地減少實現工作,而且也不必處理在多個位置上發生的競爭問題。 同時AQS在設計上充分考慮了可伸縮行,因此J.U.C中所有基於AQS構建的同步器均可以獲得這個優勢。
AQS的主要使用方式是繼承,子類通過繼承同步器並實現它的抽象方法來管理同步狀態。
AQS使用一個int類型的成員變量state來表示同步狀態,當state>0時表示已經獲取了鎖,當state = 0時表示釋放了鎖。它提供了三個方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))來對同步狀態state進行操作,當然AQS可以確保對state的操作是安全的。
AQS通過內置的FIFO同步隊列來完成線程獲取鎖的排隊工作,如果當前線程獲取同步狀態失敗(鎖)時,AQS則會將當前線程以及等待狀態等信息構造成一個節點(Node)並將其加入同步隊列,同時會阻塞當前線程,當同步狀態釋放時,則會把節點中的線程喚醒,使其再次嘗試獲取同步狀態。AQS類聲明如下:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
//methods
}
上述head和tail,就是內部的雙向同步隊列,state用來表示同步狀態。另外,AQS繼承了AbstractOwnableSynchronizer類,AbstractOwnableSynchronizer類中有一個Thread類型的成員變量,用於存儲當前持有鎖的線程。AbstractOwnableSynchronizer類提供getExclusiveOwnerThread方法,可以獲取當前持有鎖的線程。
2.1 方法說明
AQS主要提供瞭如下一些方法:
- protected final int getState()
返回同步狀態的當前值
- protected final void setState(int newState)
設置當前同步狀態
- protected final boolean compareAndSetState(int expect, int update)
CAS修改同步狀態,如果設置成功返回true,否則返回false,該方法能夠保證狀態設置的原子性
- protected boolean tryAcquire(int arg)
獨佔式獲取同步狀態,獲取同步狀態成功後,返回true,其他線程需要等待該線程釋放同步狀態才能獲取同步狀態。獲取同步狀態失敗,立即返回false
- public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException
在指定超時時間內獲取獨佔鎖同步狀態,如果當前線程在nanos時間內沒有獲取到同步狀態,那麼將會返回false,已經獲取則返回true
- protected boolean tryRelease(int arg)
獨佔式釋放同步狀態
- protected int tryAcquireShared(int arg)
共享式獲取同步狀態,返回值大於等於0則表示獲取成功,否則獲取失敗
- public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException
在指定超時時間內共享鎖同步狀態,如果當前線程在nanos時間內沒有獲取到同步狀態,那麼將會返回false,已經獲取則返回true
- protected boolean tryReleaseShared(int arg)
共享式釋放同步狀態
- protected boolean isHeldExclusively()
當前同步器是否在獨佔式模式下被線程佔用,即表示鎖是否被當前線程所獨佔
- public final void acquire(int arg)
獨佔式獲取同步狀態,如果當前線程獲取同步狀態成功,則由該方法返回。否則,將會進入同步隊列等待,直至獲取鎖
- public final void acquireInterruptibly(int arg) throws InterruptedException
與acquire(int arg)相同,但是該方法響應中斷,當前線程爲獲取到同步狀態而進入到同步隊列中,如果當前線程被中斷,則該方法會拋出InterruptedException異常並返回,不會繼續等待
- public final void acquireShared(int arg)
共享式獲取同步狀態,如果當前線程未獲取到同步狀態,將會進入同步隊列等待,與獨佔式的主要區別是在同一時刻可以有多個線程獲取到同步狀態
- public final void acquireSharedInterruptibly(int arg) throws InterruptedException
共享式獲取同步狀態,響應中斷
- public final boolean release(int arg)
釋放獨佔式同步狀態,該方法會在釋放同步狀態之後,將同步隊列中第一個節點包含的線程喚醒
- public final boolean releaseShared(int arg)
共享式釋放同步狀態
2.2 同步隊列
AQS內部維護了一個同步隊列,該隊列是一個FIFO雙向隊列,AQS依賴它來完成同步狀態的管理。當前線程獲取同步狀態失敗時,AQS則會將當前線程和等待狀態等信息構造成一個節點(Node)並將其加入到同步隊列,同時會阻塞當前線程,當同步狀態釋放時,會把首節點喚醒(公平鎖),使其再次嘗試獲取同步狀態(如果是非公平鎖,當鎖被釋放時,不一定被同步隊列首節點獲取到鎖,也有可能被正在申請鎖的線程獲取到,這就是非公平性的體現)。
同步隊列中,一個節點表示一個正在等待的線程,它保存着線程的引用(thread)、狀態(waitStatus)、前驅節點(prev)、後繼節點(next),其定義如下:
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
//methods
}
其中nextWaiter是顯示條件(Condition)用來組織所有等待(await)的線程節點的,這點在下面講顯示條件實現時在介紹。先來看一下同步隊列的組織形式:
head指向的節點比較特殊,假如當前只有一個線程等待鎖,那麼同步隊列也會有兩個節點。可以理解爲head節點是當前正在使用鎖的節點,該節點初始化時,prev域爲null,thread域爲null,waitStatus爲0。head節點的後繼節點纔是等待鎖的第一個線程對應的節點。
3. 從AQS解釋ReentrantLock實現
3.1 ReentrantLock定義
public class ReentrantLock implements Lock, java.io.Serializable {
//用於實現顯式鎖的同步對象(sync其實就是隊列同步器AQS)
private final Sync sync;
//AQS實現類
abstract static class Sync extends AbstractQueuedSynchronizer {}
//非公平鎖
static final class NonfairSync extends Sync {}
//公平鎖
static final class FairSync extends Sync{}
//默認構造函數
public ReentrantLock() {
sync = new NonfairSync();
}
//構造函數
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//methods
}
可以看到,ReentrantLock爲了複用AQS的代碼,在內部通過繼承於AQS的同步類Sync來使用AQS提供的同步隊列等特性,這也是之前講的AQS的主要使用方式是繼承,子類通過繼承同步器並實現它的抽象方法來管理同步狀態的體現。
Sync類是個抽象類,並沒有完全實現AQS的抽象方法,這是因爲ReentrantLock提供公平鎖和非公平鎖,這兩種鎖獲取鎖的策略是不同的,不同的邏輯分別在NonfairSync和FairSync中實現。 另外,通過構造函數可以看出,ReentrantLock的默認實現是非公平鎖。
先來看一下Sync、NoFairSync和FairSync的實現(這裏先省略跟獲取鎖/解鎖無關的邏輯):
abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* 抽象方法,用於公平鎖/非公平鎖子類實現不同的獲取鎖的策略
*/
abstract void lock();
/**
* 非公平嘗試獲取獨佔鎖,獲取不到立即返回false
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果鎖沒有被佔有,立即CAS嘗試佔有鎖,這就是非公平的體現
//因爲同步隊列中有可能有之前申請鎖阻塞的線程卻沒有優先獲取鎖
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果鎖已經被佔有,並且佔有鎖的線程是當前線程,直接修改鎖的重入次數state
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//此時線程是持有鎖的,直接調用set方法設置同步狀態即可
setState(nextc);
return true;
}
//除以上兩種情況,都認爲獲取鎖失敗
return false;
}
/**
* 釋放獨佔鎖同步狀態
*/
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);
}
//此時線程是持有鎖的,直接調用set方法設置同步狀態即可
setState(c);
return free;
}
}
static final class NonfairSync extends Sync {
/**
* 非公平獲取獨佔鎖,獲取不到會阻塞
*/
final void lock() {
//直接CAS修改同步狀態,非公平就體現在這,新申請鎖的線程有可能優先於同步隊列的線程獲取到鎖
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//獲取獨佔鎖,如果獲取不到會阻塞線程,直至獲取到鎖,acquire方法是AQS中定義的方法
acquire(1);
}
/**
* 非公平嘗試獲取獨佔鎖,該方法是AQS的抽象方法,方法實現直接調用Sync的nonfairTryAcquire方法
*/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {
/**
* 公平獲取獨佔鎖
*/
final void lock() {
//獲取獨佔鎖,跟非公平鎖相比,少了CAS嘗試,acquire方法是AQS中定義的方法
acquire(1);
}
/**
* 公平嘗試獲取獨佔鎖
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//這裏跟給公平鎖相比多了個條件,同步隊列中沒有等待鎖的線程,纔會通過CAS修改同步狀態
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
這裏可以看出,Sync類中定義了抽象方法lock(),在子類NoFairSync和FairSync中都給出了實現。在子類NoFairSync和FairSync中,除了實現了Sync類的lock方法,還覆蓋了父類AQS中的tryAcquire方法,這一點也好理解,tryAcquire方法用於嘗試獲取獨佔鎖,在公平鎖和非公平鎖模式下肯定是不同的,需要各自實現。
3.2 lock()
這裏以非公平鎖爲例,來分析一下非公平鎖加鎖方法lock的實現細節。ReentrantLock中lock方法實現如下:
public void lock() {
sync.lock();
}
上面我們知道,通過ReentrantLock的構造函數,會對成員變量sync賦值,並且不同的構造函數,得到的sync對象是不同的(NoFairSync/FairSync)。所以當sync是非公平鎖對象時,這裏調用的肯定是NoFairSync類的lock方法:
/**
* 非公平獲取獨佔鎖,獲取不到會阻塞
*/
final void lock() {
//直接CAS修改同步狀態,修改成功,就成功獲取了鎖
//非公平就體現在這,新申請鎖的線程有可能優先於同步隊列的線程獲取到鎖
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//獲取獨佔鎖,如果獲取不到會阻塞線程,直至獲取到鎖,acquire方法是AQS中定義的方法
acquire(1);
}
上述代碼的邏輯很簡單,當線程申請鎖時,默認會讓當前線程CAS嘗試修改同步狀態,如果修改成功,當前線程就獲取了鎖,並調用AbstractOwnableSynchronizer類的setExclusiveOwnerThread方法將持有鎖的線程設置爲當前線程。因爲上述這個直接CAS的過程,即使同步隊列已經有線程在等待鎖,鎖也有可能被後來申請鎖的線程拿到,這就是非公平的體現。當CAS修改同步狀態失敗時,就調用AQS的acquire方法獲取鎖:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire方法實現中,首先會調用tryAcquire方法嘗試獲取鎖,關於tryAcquire方法,上面講過NoFairSync和FairSync都實現了該方法,對於非公平鎖肯定調用的是NoFairSync類的tryAcquire方法:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
/**
* 非公平嘗試獲取獨佔鎖,獲取不到立即返回false
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果鎖沒有被佔有,立即CAS嘗試佔有鎖,這也是非公平的體現
//因爲同步隊列中有可能有之前申請鎖阻塞的線程卻沒有優先獲取鎖
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果鎖已經被佔有,並且佔有鎖的線程是當前線程,直接修改鎖的重入次數state
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//此時線程是持有鎖的,直接調用set方法設置同步狀態即可
setState(nextc);
return true;
}
//除以上兩種情況,都認爲獲取鎖失敗
return false;
}
tryAcquire方法實現比較簡單,這裏不多講了,關鍵點都在上面方法的註釋中。回到AQS的acquire方法,如果tryAcquire方法嘗試獲取鎖失敗,則會調用acquireQueued方法獲取鎖,acquireQueued方法的第一個參數是通過addWaiter方法獲取的:
/**
* 向同步隊列中插入一個節點
*/
private Node addWaiter(Node mode) {
//新生成一個節點node,表示當前等待鎖的線程
Node node = new Node(Thread.currentThread(), mode);
//獲取前驅節點tail(node要插入到tail後面)
Node pred = tail;
//tail非null,說明同步隊列中已經有節點,只要把node插入到當前tail節點後,並將tail設置爲node即可
if (pred != null) {
node.prev = pred;
//CAS原子方式設置tail指向
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//tail爲null(同步隊列爲空)或上述CAS設置tail指向失敗,調用enq方法入隊
enq(node);
return node;
}
/**
* 同步隊列爲空或上述CAS設置tail指向失敗時,node入隊
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
//tail爲null,表示同步隊列中無節點,這時候需要初始化
if (compareAndSetHead(new Node()))
//head指向一個空節點,該節點Thread域爲null,waitStatus爲0
//理解這點很重要,第一個等待鎖的線程節點並不是同步隊列的head節點,head節點是一個空節點
//head節點的後繼節點是第一個等待鎖的節點
tail = head;
} else {
//node入隊
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
也就是講,通過addWaiter方法,返回了新入隊的等待鎖的節點引用。這裏要重點強調一下,同步隊列的head節點其實指向的是個空節點,第一個等待鎖的線程節點是head的後繼節點。下面來看acquireQueued方法實現:
/**
* 排隊獲取鎖對象
* 走到這,說明線程嘗試獲取鎖失敗,只能排隊獲取鎖
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//這裏是個死循環,調出循環的條件是當前節點是head的後繼節點,並且嘗試獲取鎖成功
for (;;) {
//獲取node的前驅節點p
final Node p = node.predecessor();
//如果node是p的後繼節點,並嘗試獲取鎖成功
if (p == head && tryAcquire(arg)) {
//node已經獲取到鎖,則node節點修改爲同步隊列的head,並斷開node的prev連接
setHead(node);
//setHead方法已經斷開了node的prev連接,這裏在斷開之前head的next連接,之前的head節點就可以被GC回收了
p.next = null; // help GC
failed = false;
//返回interrupted標誌,interrupted表示線程在等待鎖期間是否被中斷
return interrupted;
}
//如果節點嘗試獲取鎖失敗,就要阻塞當前線程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquireQueued方法簡單的講就是讓同步隊列中的node節點獲取鎖,獲取鎖的過程是個死循環,只有等node節點獲取到鎖,才能從循環中跳出。所以node獲取鎖的過程中可能會進行多次嘗試,每次嘗試失敗時,都要阻塞自己的線程。
acquireQueued方法核心就是如下三點:
- 循環獲取鎖,可以獲取鎖的前提是node節點是head的後繼節點,並且嘗試獲取鎖成功
- 如果嘗試獲取鎖失敗就要檢查是否需要阻塞當前線程,如果node的前驅節點waitStatus是SIGNAL,則需要阻塞
- 則塞線程調用LockSupport.park方法,該方法是個響應中斷的方法,線程可以從阻塞中返回的前提是別的線程調用了unpark方法喚醒了當前線程
檢查等待鎖的線程是否需要阻塞的方法shouldParkAfterFailedAcquire實現:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//前驅節點爲SIGNAL(-1),表示前面節點的線程釋放鎖會通過unpark喚醒node,
//因此當node獲取鎖失敗時,需要調用park堵塞阻塞線程,等待被前面的節點喚醒
return true;
if (ws > 0) {
//前驅節點狀態爲CANCLE(1),表示節點代表的線程已經不需要獲取鎖,刪除前驅節點
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node; //更新next指針,刪除取消狀態的節點
} else {
//前驅節點狀態未0(初始狀態),則CAS將前驅節點狀態修改爲SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//走到這說明前驅節點waitStatus狀態不是SIGNAL,當前線程不需要park,
//在下次檢查是該方法會返回true,當前線程會park阻塞
return false;
}
總結起來就是,如果node的前驅節點waitStatus狀態是SIGNAL(-1),那麼node代表的節點就需要阻塞等待。然後來看一下重點邏輯,當線程獲取不到鎖時,是如何實現阻塞的,就是parkAndCheckInterrupt方法:
/**
* 阻塞線程,返回線程在阻塞期間是否被中斷
*/
private final boolean parkAndCheckInterrupt() {
//阻塞線程,調用該方法後,線程會釋放CPU,進入阻塞狀態
LockSupport.park(this);
//走到這裏,說明線程已經從阻塞中返回了
//前提是其他線程調用了unpark方法,喚醒了當前線程
return Thread.interrupted(); //返回線程中斷狀態,並清除中斷狀態
}
如果該方法返回true,說明線程在阻塞等待鎖期間被中斷過。但是從acquireQueued方法實現可以看出,即使線程在等待期間被中斷了,也只是設置了中斷標記局部變量interrupted,並不會從等待中返回,所以acquireQueued是不響應中斷的,鎖是否相應中斷,其實區別就在parkAndCheckInterrupt方法返回true時的處理上。
到這裏加鎖過程基本就講完了,回到最開始的幾個問題,我們至少可以回答前兩個了:
- 問題1:顯式鎖底層是基於CAS實現,細節是怎樣的?
顯示鎖是基於AQS實現的,AQS維護鎖的狀態是通過內部int類型的成員變量state來表示的,當state>0時表示已經獲取了鎖,當state = 0時表示釋放了鎖。加鎖的過程就是通過CAS修改state成員變量的過程。
- 問題2:顯式鎖跟synchronized類似,無法獲取鎖時,會阻塞當前線程,如何實現阻塞的?
顯示鎖中,當嘗試獲取鎖失敗時,會調用acquireQueued方法排隊獲取鎖,這個隊列就是AQS內部維護的雙向同步隊列,只有當前線程對應的節點是雙向隊列head節點的後繼節點並且嘗試獲取鎖成功時,才表示線程最終獲取鎖成功,否則就需要繼續阻塞等待,直至滿足上述條件。阻塞等待是通過LockSupport.park方法實現的。
3.3 tryLock()
之前的文章中講過,顯示鎖中提供tryLock方法,可以避免死鎖。在持有一個鎖,獲取另一個鎖,獲取不到的時候,可以釋放已持有的鎖,給其他線程機會獲取鎖。也就是講,在tryLock方法中,如果嘗試獲取鎖失敗時,並不會去排隊獲取鎖並阻塞線程,而是會直接返回果false(獲取鎖失敗)。這裏以非公平鎖的tryLock方法爲示例:
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
可以看到tryLock就是直接通過調用Sync類的nofairTryAcquire方法實現的,nofairTryAcquire方法的實現之前已經講過,其實就是非公平方式嘗試獲取鎖:
/**
* 非公平嘗試獲取獨佔鎖,獲取不到立即返回false
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果鎖沒有被佔有,立即CAS嘗試佔有鎖,這也是非公平的體現
//因爲同步隊列中有可能有之前申請鎖阻塞的線程卻沒有優先獲取鎖
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果鎖已經被佔有,並且佔有鎖的線程是當前線程,直接修改鎖的重入次數state
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//此時線程是持有鎖的,直接調用set方法設置同步狀態即可
setState(nextc);
return true;
}
//除以上兩種情況,都認爲獲取鎖失敗
return false;
}
也就是講noFairTryAcquire的前提條件是(以下條件滿足一個即可):
- 線程在申請鎖時,鎖沒有被其它線程持有,並且當前線程成功通過CAS修改了同步狀態
- 有線程持有鎖,但是持有鎖的線程是當前線程,此次加鎖只是一次重入,只需要將同步狀態加1即可
除了上述兩種情況,都視爲獲取鎖失敗,直接返回false。
回到最開始的問題3:顯式鎖提供還可以提供tryLock的操作,獲取不到鎖立即返回,如何實現?
顯示鎖tryLock與lock方法相比,方法最開始都會通過通過noFairTryAcquire方法嘗試獲取鎖,但是對於嘗試獲取鎖失敗的情況,tryLock直接返回false,並不會阻塞當前線程,lock方法會調用acquireQueued方法,排隊獲取鎖,如果獲取不到鎖就阻塞線程,直到成功獲取鎖。
3.4 lockInterruptibly
上面講lock方法的時候,我們知道lock方法獲取鎖是不響應中斷的。也就是講在lock方法在獲取不到鎖阻塞等待的過程中,如果線程被中斷,線程並不會從中斷中返回,還是會繼續等待獲取鎖。但是顯示鎖提供了另一個加鎖方法lockInterruptibly,可以響應中斷,如下:
public void lockInterruptibly() throws InterruptedException {
//直接調用AQS的acquireInterruptibly方法
sync.acquireInterruptibly(1);
}
/**
* 響應中斷獲取獨佔鎖
*/
public final void acquireInterruptibly(int arg) throws InterruptedException {
//獲取中斷狀態,並清除中斷標識,如果線程被中斷,直接拋異常
if (Thread.interrupted())
throw new InterruptedException();
//先嚐試獲取獨佔鎖,如果獲取鎖成功,方法直接結束
//否則調用doAcquireInterruptibly方法,以響應中斷的方式獲取獨佔鎖
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
/**
* 以響應中斷模式獲取獨佔鎖
* 這裏的實現跟acquireQueued基本一致,唯一的區別在於park阻塞等待過程中,線程被中斷的處理上
*/
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;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果線程阻塞等待鎖的過程中被中斷,直接拋異常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
這裏可以看到,響應中斷獲取鎖和普通獲取鎖的方法實現上基本是一致的,唯一的區別就在park阻塞等待過程中,如果線程被中斷的處理上。lock方法調用的acquireQueued方法,在發生中斷時,只是記了中斷標記,並沒有中斷線程,還是會繼續等待鎖。但是lockInterruptibly方法調用的doAcquireInterruptibly方法,在發生中斷時,直接拋InterruptedException,放棄繼續等待鎖,所以可以響應中斷。這也是最開始的問題4的答案。
3.5 unLock
unLock方法就是解鎖,按照之前加鎖的過程,我們大概能猜出解鎖操作需要做的事情:修改state同步變量、unpark喚醒在同步隊列等待鎖的線程。下面來看一下unLock方法實現:
public void unlock() {
//直接調用ReentrantLock類的內部類Sync類的release方法釋放鎖
sync.release(1);
}
/**
* 釋放線程持有的鎖
*/
public final boolean release(int arg) {
//嘗試釋放鎖(修改同步狀態state)
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
/**
* 嘗試釋放鎖
*/
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//如果當前線程並沒有持有鎖,拋異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//鎖是否被完全釋放(因爲可以重入)
boolean free = false;
if (c == 0) {
//如果鎖完全釋放,將持有鎖的線程設置爲null
free = true;
setExclusiveOwnerThread(null);
}
//此刻線程還持有鎖,所以直接調用set方法修改state即可,不用CAS
setState(c);
return free;
}
/**
* 鎖被完全釋放後,unpark喚醒等待隊列中等待的線程
*/
private void unparkSuccessor(Node node) {
//顯式獨佔鎖解鎖時,這裏的node爲雙向同步隊列的head指向的節點
int ws = node.waitStatus;
//如果head節點的waitStatus狀態爲SIGNAL(-1),說明後面的節點需要被unpark喚醒競爭鎖
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//尋找node後第一個沒有被取消的節點
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;
}
//喚醒node後第一個沒有被取消的節點
if (s != null)
LockSupport.unpark(s.thread);
}
釋放鎖,也就是修改AQS同步對象state,並unpark喚醒同步隊列中等待鎖的線程的過程。
3.6 公平和非公平性
關於公平和非公平的實現,看似一個非常複雜的問題,但ReentrantLock實現方式是很簡單的。根本區別就在ReentrantLock類的內部類FairSync和NoFairSync方法tryAcquire實現上,對於非公平鎖,允許後請求鎖的線程直接獲取鎖,即使同步隊列中已經有線程在等待。而對於公平鎖而言,如果同步隊列中有等待鎖的線程,必須入隊等待,等隊頭的節點一一獲取鎖並釋放後,才能獲取鎖。代碼之前都解釋過:
- 非公平鎖
/**
* 非公平嘗試獲取獨佔鎖,該方法是AQS的抽象方法,方法實現直接調用Sync的nonfairTryAcquire方法
*/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
/**
* 非公平嘗試獲取獨佔鎖,獲取不到立即返回false
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果鎖沒有被佔有,立即CAS嘗試佔有鎖,這就是非公平的體現
//因爲同步隊列中有可能有之前申請鎖阻塞的線程卻沒有優先獲取鎖
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果鎖已經被佔有,並且佔有鎖的線程是當前線程,直接修改鎖的重入次數state
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//此時線程是持有鎖的,直接調用set方法設置同步狀態即可
setState(nextc);
return true;
}
//除以上兩種情況,都認爲獲取鎖失敗
return false;
}
- 公平鎖
/**
* 公平嘗試獲取獨佔鎖
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//這裏跟給公平鎖相比多了個條件,同步隊列中沒有等待鎖的線程,纔會通過CAS修改同步狀態
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
這裏問題5也能解釋了。
4. 從AQS解釋顯示條件Condition實現
上篇文章介紹過顯示條件Condition的使用,其實顯示條件的實現也是基於AQS實現的,這裏我們分析顯示條件是如何實現的。在看源碼時,我自己有一個技巧,在真正看之前,先徹底搞明白這個東西到底是幹嘛的,需要做哪些工作才能完成這些功能。然後帶着疑問去看源碼,這樣往往比較好理解。
回到顯示條件,我們肯定知道顯示條件的await方法的作用就是讓線程釋放鎖並進入WAITING狀態等待,直至其他線程調用signal喚醒重新去競爭鎖。能調用await和signal的前提是,當前線程是持有鎖的。
顯示條件的實現依賴於一個單向隊列,隊列的每個節點複用了上文同步隊列的Node節點(爲了區別於之前的同步隊列,我們之後管這個單向隊列叫Condition隊列)。Node類中有一個Node類型的成員變量nextWaiter就是Condition隊列中使用的(在同步操作中並沒有使用nextWaiter)。在JUC中,不止一個類要使用到顯式鎖,所以AQS中提供了一個內部類ConditionObject,在ConditionObject類中實現了await、signal的邏輯。ConditionObject類聲明如下:
public class ConditionObject implements Condition, java.io.Serializable {
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
//構造函數
public ConditionObject() { }
//methods,包括await、signal等方法
}
4.1 await
public final void await() throws InterruptedException {
// 如果等待前中斷標誌位已被設置,直接拋異常
if (Thread.interrupted())
throw new InterruptedException();
// 1.爲當前線程創建節點,加入條件等待隊列
Node node = addConditionWaiter();
// 2.釋放持有的鎖
int savedState = fullyRelease(node);
int interruptMode = 0;
// 3.放棄CPU,進行等待,直到被中斷或isOnSyncQueue變爲true
// isOnSyncQueue爲true表示節點被其他線程從Condition隊列
// 移到了同步隊列,等待的條件已滿足
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 4.重新獲取鎖
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//清除Condition隊列中所有CANCELLED狀態的節點
if (node.nextWaiter != null)
unlinkCancelledWaiters();
// 5.處理中斷,拋出異常或設置中斷標誌位
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
可以看到,顯示條件的實現是通過一個單向Condition隊列實現的,await方法首先會創建一個Node節點並插入到Condition隊列尾部,然後釋放對鎖的持有,park釋放CPU資源進入WAITING狀態,直至當前節點從Condition隊列轉移至同步隊列(其他線程調用了signal方法,將node添加到同步隊列,並喚醒node對應的線程)。線程會喚醒後會調用acquireQueued方法排隊獲取鎖,獲取鎖成功後才能從await方法返回。
4.2 awaitNanos
public final long awaitNanos(long nanosTimeout) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
long lastTime = System.nanoTime();
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
//等待超時,將節點從條件等待隊列移到外部的鎖等待隊列
transferAfterCancelledWait(node);
break;
}
//限定等待的最長時間
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
long now = System.nanoTime();
//計算下次等待的最長時間
nanosTimeout -= now - lastTime;
lastTime = now;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return nanosTimeout - (System.nanoTime() - lastTime);
}
實現跟await基本一致,不同的是,添加了一個超時時間控制。
4.3 signal
public final void signal() {
//驗證當前線程持有鎖
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//調用doSignal將Condition隊列中第一個節點添加到同步隊列,並unpark喚醒節點對應的線程
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
調用signal方法後,會將Condition隊列中等待的第一個節點添加到同步隊列並unpark喚醒線程,這時候await方法會從park中返回去競爭鎖。
5. 從AQS解釋ReentrantReadWriteLock實現
前面一篇文章介紹了讀寫鎖ReentrantReadWriteLock的使用細節,我們瞭解到ReentrantReadWriteLock相比於ReentrantLock是一種更加細粒度的鎖,將讀取與寫入分開處理,在讀取數據之前必須獲取用來讀取的鎖定,而寫入的時候必須獲取用來寫入的鎖定。並且允許多個線程同時獲取讀鎖,但是同時只允許一個線程獲取寫鎖。從而達到“讀讀”不互斥、“讀寫”互斥、“寫寫”互斥的效果。
ReentrantReadWriteLock在實現上也是通過AQS實現的,通過上面介紹的ReentrantLock源碼分析,我們知道AQS通過sate狀態進行鎖的獲取與釋放,同時構造了雙向FIFO同步隊列進行線程節點的等待,線程節點通過waitStatus來判斷自己需要掛起還是喚醒去獲取鎖。如果已經掌握了AQS的原理,那麼ReentrantReadWriteLock的實現也是非常好理解的。先看一下ReentrantReadWriteLock類聲明:
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends Sync {}
static final class FairSync extends Sync {}
public static class ReadLock implements Lock, java.io.Serializable {}
public static class WriteLock implements Lock, java.io.Serializable {}
//other methods
}
簡單介紹一下ReentrantReadWriteLock內部類的作用:
- Sync:繼承AQS,鎖功能的主要實現者
- FairSync:繼承Sync,實現公平鎖
- NofairSync:繼承Sync,主要實現非公平鎖
- ReadLock:讀鎖,通過成員變量sync實現讀鎖功能
- WriteLock:寫鎖,通過成員變量sync實現寫鎖功能
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/ static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
//……
}
ReentrantReadWriteLock的內部類Sync繼承了AQS,主要提供了鎖功能的實現,但是跟ReentrantLock的內部類Sync不同的是上面這幾個final類型的int型成員變量,除此之外,方法定義基本一致(因爲都繼承了AQS),這裏提前介紹一下這幾個成員變量的作用。我們知道AQS是通過int型成員變量state來維護同步狀態的,當state>0時表示已經獲取了鎖,當state = 0時表示釋放了鎖。但是在讀寫鎖中,讀鎖和寫鎖是分開的,如何使用一個變量state表示兩種鎖的同步狀態?答案是通過位運算,將int型成員變量的32爲分爲高16位和低16位,高16位表示讀鎖,低16位表示寫鎖。所以再回頭看上述Sync類的方法sharedCount,其實就是獲取c低16位的值,即讀鎖被持有的數量。exclusiveCount,其實就是獲取c高16位的值,即寫鎖被持有的數量。這裏再簡單回顧一下上篇文章講的讀鎖和寫鎖的進入條件:
- 線程進入讀鎖的條件(滿足其中一個即可):
- 沒有任何線程持有寫鎖
- 有線程持有寫鎖,但是持有寫鎖的線程是當前線程
- 線程進入寫鎖的條件(滿足其中一個即可)
- 沒有任何線程持有讀鎖或寫鎖
- 有線程持有寫鎖,但是持有寫鎖的線程是當前線程
5.1 寫鎖lock
/**
* 阻塞獲取寫鎖
*/
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
看過上面講的ReentrantLock獲取鎖,寫鎖lock的實現代碼一定非常熟悉,實現跟ReentrantLock的lock方法完全一致。也是調用了Sync類的acquire方法(繼承自AQS),然後在acquire方法中通過回調子類的tryAcquire方法嘗試獲取鎖,其他的邏輯都不再單獨介紹了,這裏重點介紹ReentrantReadWriteLock內部類Sync的tryAcquire邏輯實現,tryAcquire其實就是非阻塞嘗試獲取寫鎖,如下:
protected final boolean tryAcquire(int acquires) {
//獲取當前線程
Thread current = Thread.currentThread();
//獲取同步狀態
int c = getState();
//獲取寫鎖持有的數量
int w = exclusiveCount(c);
//同步狀態不等於0,說明已經鎖已經被獲取過了(可以是讀鎖也可以是寫鎖)
if (c != 0) {
//1. c!=0說明是有鎖被獲取的,那麼w==0,
//說明寫鎖是沒有被獲取,也肯定是讀鎖被獲取了,
//由於寫鎖和讀鎖的互斥,所以獲取獲取寫鎖失敗,返回false
//2. w!=0,說明寫鎖被獲取了,但是current != getExclusiveOwnerThread() ,
// 說明是被別的線程獲取了,return false;
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//走到這說明w!=0 && current == getExclusiveOwnerThread()
//當前持有寫鎖的線程跟請求寫鎖的線程是同一線程,本次加鎖是寫鎖重入
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//寫鎖重入,直接調用setState方法修改同步狀態
setState(c + acquires);
return true;
}
// c == 0,說明沒有線程持有鎖,CAS嘗試獲取鎖
//writerShouldBlock方法在FairSync和NoFairSync中各自實現,分別保證公平和非公平性
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
關鍵點都在註釋中說明了,比較好理解。我們回顧一下ReentrantLock中tryAcquire的實現,ReentrantLock中內部類FairSync和NoFairSync都重寫了AQS的tryAcquire方法,但是實現邏輯都是首先處理c==0(鎖沒有被獲取)直接CAS(公平鎖需要另外判斷同步隊列是否有排隊的線程),然後處理c!=0,判斷是否爲鎖重入。ReentrantReadWriteLock中,只在Sync類中重寫了tryAcquire邏輯,FairSync和NoFairSync中並沒有重寫該方法,而是提供了writerShouldBlock和readerShouldBlock方法供Sync類的tryAcquire方法調用,判斷是否可以直接CAS競爭鎖,從而體現公平性和非公平性。在實現邏輯上,跟ReentrantLock中是一致的,只不過在ReentrantReadWriteLock中先判斷的c!=0的情況,然後判斷c==0的情況,跟ReentrantLock中處理順序有點差別。
static final class NonfairSync extends Sync {
/**
* 非公平鎖模式下請求寫鎖的線程是否需要排隊獲取鎖
*/
final boolean writerShouldBlock() {
//直接競爭,不保證公平性
return false;
}
/**
* 非公平鎖模式下請求讀鎖的線程是否需要排隊獲取鎖
*/
final boolean readerShouldBlock() {
//理論上也可以直接返回false,但是爲了避免同步隊列中等待的寫鎖飢餓
//所以做了額外的判斷,這裏犧牲了一定的不公平性
return apparentlyFirstQueuedIsExclusive();
}
}
static final class FairSync extends Sync {
/**
* 公平鎖模式下請求寫鎖的線程是否需要排隊獲取鎖
*/
final boolean writerShouldBlock() {
//如果同步隊列中有排隊的線程,當前線程請求鎖就需要排隊
return hasQueuedPredecessors();
}
/**
* 公平鎖模式下請求讀鎖的線程是否需要排隊獲取鎖
*/
final boolean readerShouldBlock() {
//如果同步隊列中有排隊的線程,當前線程請求鎖就需要排隊
return hasQueuedPredecessors();
}
}
5.2 讀鎖lock
/**
* 阻塞式獲取共享讀鎖
*/
public void lock() {
sync.acquireShared(1);
}
阻塞式獲取共享讀鎖的實現是通過調用ReentrantReadWriteLock的內部類Sync的acquireShared方法(繼承自AQS)實現的。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
ReentrantReadWriteLock的內部類Sync重寫了AQS的tryAcquireShared方法,所以最終會調用Sync類的tryAcquireShared:
/**
* 非阻塞嘗試獲取讀鎖
*/
protected final int tryAcquireShared(int unused) {
//獲取當前線程
Thread current = Thread.currentThread();
//獲取同步狀態
int c = getState();
//exclusiveCount寫鎖持有數目!=0,並且持有寫鎖的線程非當前線程,
//讀寫互斥,return -1,表示嘗試獲取讀鎖失敗
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//走到這裏說明寫鎖沒有被持有,或者寫鎖被持有寫鎖被持有但是持有寫鎖的線程是當前線程
//這兩種情況,都說明允許獲取讀鎖
int r = sharedCount(c); //獲取讀鎖持有數目
//CAS獲取讀鎖,獲取成功return 1
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//處理CAS失敗和重入讀鎖的情況,不阻塞
return fullTryAcquireShared(current);
}
如果tryAcquireShared方法嘗試獲取鎖失敗,就會調用AQS的diAcquiredShared方法排隊獲取共享讀鎖,邏輯跟之前講的acquiredShared方法非常相似:
/**
* 嘗試獲取共享讀鎖
*/
private void doAcquireShared(int arg) {
//等待讀鎖節點入同步隊列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
//死循環等待獲取鎖,跳出循環的前提是,當前節點是head的後繼節點,並且嘗試獲取鎖成功
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;
}
}
//如果沒有獲取到鎖,就要park阻塞當前請求鎖的線程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
跟acquireQueued的區別就在於嘗試獲取鎖的邏輯和獲取到鎖的後續操作。
5.3 寫鎖釋放unlock
/**
* 釋放獨佔寫鎖
*/
public void unlock() {
//調用AQS的release方法
sync.release(1);
}
/**
* AQS釋放獨佔鎖,該方法在ReentrantLock和ReentrantReadWriteLock都有使用
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
/**
* ReentrantReadWriteLock內部類Sync重寫tryRelease方法
*/
protected final boolean tryRelease(int releases) {
//如果當前請求釋放寫鎖的線程不是持有寫鎖的線程,拋異常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//釋放後鎖的持有數量,因爲寫鎖在低16位,所以直接減就可以,不用移位
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
//如果寫鎖持有數量爲0,說明寫鎖已經被完全釋放
//講寫鎖持有線程設置爲null
if (free)
setExclusiveOwnerThread(null);
//這裏鎖還沒有釋放,直接通過set方法設置同步狀態
setState(nextc);
return free;
}
5.3 讀鎖釋放unlock
/**
* ReentrantReadWriteLock釋放共享讀鎖
*/
public void unlock() {
//直接調用AQS的releaseShared方法釋放共享鎖
sync.releaseShared(1);
}
/**
* 釋放共享鎖
*/
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)//如果是首次獲取讀鎖,那麼第一次獲取讀鎖釋放後就爲空了
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) { //表示全部釋放完畢
readHolds.remove(); //釋放完畢,那麼久把保存的記錄次數remove掉
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
int c = getState();
// nextc 是 state 高 16 位減 1 後的值
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc)) //CAS設置狀態
return nextc == 0; //這個判斷如果高16位減1後的值==0,那麼就是讀狀態和寫狀態都釋放了
}
}
關於ReentrantReadWriteLock實現分析,就先講到這裏。其它比如tryLock、lockInterruptibly就不一一講解了,實現思想跟ReentrantLock類似,只不過ReentrantReadWriteLock對同步狀態分爲高16位和低16位分別處理。同時通過代碼分析可以知道,無論是ReentrantLock還是ReentrantReadWriteLock都最大程度上覆用了AQS提供的方法,可以說設計非常精巧。除了ReentrantLock和ReentrantReadWriteLock,像之前文章介紹的同步工具Semaphore也使用了AQS,可以講AQS是JUC的基礎。
5.4 讀寫鎖同步狀態分析
上面講到讀寫鎖ReentrantReadWriteLock中,將同步狀態分爲高16位和低16位,分別表示讀鎖和寫鎖,讀鎖位共享鎖寫鎖位獨佔鎖,從而實現讀寫鎖分離。這裏我們就來介紹一下ReentrantReadWriteLock是如何實現實現分別維護讀寫鎖狀態的。
static final int SHARED_SHIFT = 16;
//65536
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//65535
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//65535
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** 獲取讀的狀態 */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** 獲取寫鎖的獲取狀態 */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
我們按照圖示內容的數據進行運算,圖示的32位二進制數據爲:00000000000000100000000000000011
- 讀狀態獲取
00000000000000100000000000000011 >>> 16
,無符號右移16位(獲取高16位值),結果如下:00000000000000000000000000000010
,換算成10進制數等於2,說明讀狀態爲:2。
- 寫狀態獲取
00000000000000100000000000000011 & 65535
,轉換成2進制運算爲00000000000000100000000000000011 & 00000000000000001111111111111111
這裏其實就是獲取低16位值,運算結果爲: 00000000000000100000000000000011
,換算成10進製爲3。
這裏的設計非常巧妙,在不修改AQS的代碼前提下,僅僅通過原來的State變量就滿足了讀鎖和寫鎖的分離。
參考鏈接:
1. Java API
2. 《Java編程的邏輯》