ReentrantLock原始碼分析
ReentranLock從字面上理解就是可重入鎖,它支援同一個執行緒對資源的重複加鎖,也是我們平時在處理java併發情況下用的最多的同步元件之一(還有volatile,synchronized等)。
一般ReentrantLock的使用方式如下:
public class LockDemo {
private Lock lock = new ReentrantLock();
private int cnt = 0;
public void setCnt(){
lock.lock();
try{
//..........
cnt++;
//..........
} finally {
lock.unlock();
}
}
}
要了解一個類的功能,從它的內部方法與成員變數看起。
ReentranLock是通過Sync及其子類來實現的同步控制。也就是說ReentrantLock的同步功能是由Sync代理的。同時,ReentranLock也是通過FairSync與NonfairSync來支援ReentranLock在獲取鎖時的公平與非公平性選擇。實際上,JavaDoc裡對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
一. 公平與非公平鎖
公平鎖與非公平鎖的區別:
公平鎖 | 多執行緒併發獲取鎖時,按照等待時間排序獲得鎖,等待時間最久的執行緒先獲得鎖 |
---|---|
非公平鎖 | 獲取鎖時不考慮排隊問題,直接嘗試獲取鎖 |
我們可以沿著ReentrantLock的呼叫鏈來簡單分析下公平鎖與非公平鎖的實現區別。
1.lock方法原始碼分析
首先,ReentrantLock會呼叫lock方法,該方法只是簡單的呼叫成員變數sync的lock方法。
public void lock() {
sync.lock();
}
在這裡,由於sync的不同,會出現分支,FairSync與NonfairSync都有自己的分支。預設情況下,ReentrantLock是非公平鎖,而為了使用公平鎖,可以在初始化ReentrantLock時傳入引數進行選擇。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
我們先看一下非公平鎖的分支:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
先是直接嘗試通過CAS原子的設定state變數[如果state變數為0則將其設定為1,設定成功表明當前執行緒成功獲取了鎖],成功後設定當前執行緒為鎖的持有者,否則繼續呼叫AbstractQueuedSynchronizer的acquire方法。
而對於公平鎖來說,十分簡單,直接呼叫AbstractQueuedSynchronizer的acquire方法。
final void lock() {
acquire(1);
}
FairSync與NonfairSync在acquire方法方法出匯合。繼續追蹤AbstractQueuedSynchronizer的acquire方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
該方法的基本邏輯是:先呼叫tryAcquire方法嘗試獲取鎖,如果失敗,會構造一個獨佔的Node節點加入等待佇列。對於tryAcquire方法,基於FairSync與NonfairSync又有兩種實現。對於非公平鎖,會執行該方法:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//獲取狀態變數
if (c == 0) {//表明沒有執行緒佔有該同步狀態
if (compareAndSetState(0, acquires)) {//以原子方式設定該同步狀態
setExclusiveOwnerThread(current);//該執行緒擁有該FairSync同步狀態
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;
}
而對於公平鎖,該方法則是這樣:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//先判斷該執行緒節點是否是佇列的頭結點
//是則以原子方式設定同步狀態,獲取鎖
//否則失敗返回
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;
}
2.unlock方法原始碼分析
ReentrantLock呼叫unlock方法:
public void unlock() {
sync.release(1);//每次呼叫unlock方法,只對state變數減1操作,所以多次加鎖後需要多次解鎖
}
由sync呼叫release方法釋放鎖。
public final boolean release(int arg) {
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) {
free = true;
setExclusiveOwnerThread(null);//state減為0,表明目前沒有執行緒持有該鎖
}
setState(c);
return free;
}
我們可以觀察到,對AQS的state成員變數的操作都是不用加鎖的,原因在於state是一個volatile變數,可以從語言層面保持改變數線上程間的可見性。
private volatile int state;
protected final int getState() {
return state;
}
到了這裡,我們可以總結一下公平鎖與非公平鎖的實現差別了:
- 非公平鎖一開始會直接嘗試設定獲取同步狀態變數,獲取鎖
- 在tryAcquire方法中,公平鎖會先判斷當前執行緒是否為在鎖的等待佇列的頭結點,由於該佇列是一個FIFO佇列,故這樣判斷可以實現公平鎖鎖追求的公平性-即等待時間最長的鎖先獲得鎖。
總結: - 我們平時使用ReentrantLock時,預設是使用非公平鎖,因為在實際情況中,公平鎖往往沒有非公平鎖的效率高。非公平鎖的吞吐量會更高一些。
- 公平鎖機制能夠減少'飢餓'的發生,按佇列順序,排隊的執行緒總能夠得到鎖。而非公平鎖可能導致在佇列裡的某些執行緒長時間無法獲取鎖。
二. AbstractQueuedSynchronizer的實現分析
在前面沿著ReentrantLock的呼叫鏈來分析公平鎖與非公平鎖的實現時,我們發現ReentrantLock的很多工作最終都是由AbstractQueuedSynchronizer類(以後稱AQS)完成的,比如FIFO同步佇列的管理操作、同步狀態變數的設定、CAS操作等都是由AQS完成的,AQS可以說是ReentrantLock實現同步功能的最基礎的框架類(其實,通過分析JDK中concurrent包裡提供的其它同步工具,我們會發現它們基本都把同步工作交給了AQS稱它為基礎框架類並不為過)。下面我們就通過原始碼的分析來了解AQS是如何做到這些工作的。
首先,我們可以看一下,所有的Sync都是繼承自AQS的。
abstract static class Sync extends AbstractQueuedSynchronizer
這一系列Sync類的繼承關係如圖 :
AQS裡的方法特別多,我們從AQS的幾個重要的成員變數看起,下圖是AQS的Outline,紅框標記的head,tail兩個Node節點,根據前面的瞭解,我們馬上就能聯想到,這就是AQS管理的FIFO等待佇列,用來處理獲取鎖狀態失敗的執行緒-獲取鎖狀態失敗的執行緒會被放入該佇列,等待再次嘗試獲取鎖。而state成員變數,代表著鎖的同步狀態,一個執行緒成功獲得鎖,這個行為的實質就是該執行緒成功的設定了state變數的狀態。
下面我們詳細分析一下AQS是如何管理等待佇列的。
首先,我們要看一下Node節點這個資料結構是如何的,因為正是它構成了AQS的等待佇列。Node主要有一下幾個成員變數:
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
//......
}
簡單分析一下,一個waitStatus表示Node節點的一些狀態,pre/next表示該佇列是由雙向連結串列組成,thread表示是該執行緒入隊等待獲取鎖。對於waitStatus,該Node節點規定了6中狀態。
SHARED | 表示節點處於共享模式,該模式會在AQS提供的acquire/releaseShared介面中使用,而該介面又會在能夠共享的同步元件中使用,如讀寫鎖中的讀鎖等。 |
---|---|
EXCLUSIVE | 表示節點處於獨佔模式,ReentrantLock就是使用這種模式。 |
CANCELLED(1) | 由於同步佇列中 等待的執行緒等待超時或者被中斷,需要從同步佇列中取消等待,節點進入該狀態不會變化。 |
SIGNAL(-1) | 後續節點處於等待狀態,而當前節點的執行緒如果釋放了同步狀態或被取消,將會會通知後繼節點,是後繼節點的執行緒得意繼續執行。 |
CONDITION(-2) | 節點在等待佇列中,節點 執行緒等待在Condition上,當其他執行緒對Condition呼叫了signal方法時,該節點將會從等待佇列中轉移到同步佇列 ,加入到對同步狀態的獲取中。 |
PROPAGATE(-3) | 表示下一次共享式 同步狀態獲取將會 無條件被傳播下去 |
1.AQS的acquire方法的分析
根據AQS中的 acquire方法,沒有成功獲取同步狀態的執行緒會被加入同步等待佇列的尾部。具體步驟是:
- 先建立執行緒的節點並加入同步佇列
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
//CAS方式設定佇列的尾節點
//成功則設定該節點前、後向指標
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);//CAS方式設定等待佇列尾節點失敗
return node;
}
//入隊
private Node enq(final Node node) {
for (;;) {//自旋CAS設定尾節點
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- 節點入隊後,自旋嘗試獲取同步狀態
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);
}
}
- 前驅節點非佇列的頭結點,判斷前驅結點waitStatus為SIGNAL,等待該節點釋放鎖
//判斷node節點關聯的執行緒是否可以阻塞,判斷標準就是node的前置節點pred是否處於SIGNAL狀態
//如果是,則當前節點node可以睡眠等待前置節點執行緒釋放鎖來喚醒自己(unparking)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.node
*/
do {
node.prev = pred = pred.prev;//節點的前置節點處於cancel狀態,需要迴圈跳過這些節點
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- 前驅節點處於SIGNAL狀態,node節點可以睡眠
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
//該方法會是當前執行緒暫時不被排程,直到發生三種情況:
//1.別的執行緒對該執行緒呼叫unpark操作
//2.別的執行緒終端該執行緒
//3.莫名返回
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
unsafe.park(false, 0L);//呼叫native方法park阻塞當前執行緒
setBlocker(t, null);
}
/**
* Block current thread, returning when a balancing
* <tt>unpark</tt> occurs, or a balancing <tt>unpark</tt> has
* already occurred, or the thread is interrupted, or, if not
* absolute and time is not zero, the given time nanoseconds have
* elapsed, or if absolute, the given deadline in milliseconds
* since Epoch has passed, or spuriously (i.e., returning for no
* "reason"). Note: This operation is in the Unsafe class only
* because <tt>unpark</tt> is, so it would be strange to place it
* elsewhere.
*/
public native void park(boolean isAbsolute, long time);
根據上面的原始碼分析,我們可以畫出AQS獨佔式獲取同步狀態的流程(即acquire方法的流程)
2.AQS的release方法分析
public final boolean release(int arg) {
//tryRelease方法修改同步變數,直至state為0表明現在沒有執行緒佔有同步狀態
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//喚醒頭結點的後繼節點
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
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);//喚醒後繼節點執行緒
}
public static void unpark(Thread thread) {
if (thread != null)
unsafe.unpark(thread);
}
3.最後
通過對AQS的原始碼分析,我們發現,對於同步佇列,每一個節點之間是沒有感知的,每個執行緒在嘗試獲取同步狀態失敗後,都會走一遍獨佔式獲取同步狀態的流程(見圖2-1),包括加入佇列尾部,進入等待狀態,或者被前驅喚醒等,各個佇列節點的獨立工作,構成了多執行緒爭搶設定AQS同步狀態的場景,獲得AQS的同步狀態就代表著執行緒獲取了鎖,這就是AQS的終極奧祕。。。
相關文章
- 【JDK】JDK原始碼分析-ReentrantLockJDK原始碼ReentrantLock
- ReentrantLock解析及原始碼分析ReentrantLock原始碼
- JUC之ReentrantLock原始碼分析ReentrantLock原始碼
- ReentrantLock原始碼ReentrantLock原始碼
- ReentrantLock原始碼解析ReentrantLock原始碼
- Java 併發程式設計(九) -- ReentrantLock 原始碼分析Java程式設計ReentrantLock原始碼
- ReentrantLock原始碼解讀ReentrantLock原始碼
- java原始碼 - ReentrantLock之FairSyncJava原始碼ReentrantLockAI
- java原始碼-ReentrantLock之FairSyncJava原始碼ReentrantLockAI
- java原始碼-ReentrantLock之NonfairSyncJava原始碼ReentrantLockAI
- 深入淺出ReentrantLock原始碼解析ReentrantLock原始碼
- Java讀原始碼之ReentrantLock(2)Java原始碼ReentrantLock
- ReentrantLock介紹及原始碼解析ReentrantLock原始碼
- ReentrantLock可重入鎖——原始碼詳解ReentrantLock原始碼
- ReentrantLock 公平鎖原始碼 第0篇ReentrantLock原始碼
- ReentrantLock 公平鎖原始碼 第1篇ReentrantLock原始碼
- ReentrantLock 公平鎖原始碼 第2篇ReentrantLock原始碼
- synchronized實現原理及ReentrantLock原始碼synchronizedReentrantLock原始碼
- AQS原始碼深入分析之獨佔模式-ReentrantLock鎖特性詳解AQS原始碼模式ReentrantLock
- ReentrantLock原理分析ReentrantLock
- 多執行緒高併發程式設計(3) -- ReentrantLock原始碼分析AQS執行緒程式設計ReentrantLock原始碼AQS
- 從ReentrantLock詳解AQS原理原始碼解析ReentrantLockAQS原始碼
- Java併發之ReentrantLock原始碼解析(三)JavaReentrantLock原始碼
- Java併發之ReentrantLock原始碼解析(四)JavaReentrantLock原始碼
- Java併發之ReentrantLock原始碼解析(一)JavaReentrantLock原始碼
- Java併發之ReentrantLock原始碼解析(二)JavaReentrantLock原始碼
- Retrofit原始碼分析三 原始碼分析原始碼
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼
- 集合原始碼分析[3]-ArrayList 原始碼分析原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- 【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析JDK原始碼BloC
- 集合原始碼分析[1]-Collection 原始碼分析原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- 以太坊原始碼分析(36)ethdb原始碼分析原始碼
- 以太坊原始碼分析(38)event原始碼分析原始碼
- 以太坊原始碼分析(41)hashimoto原始碼分析原始碼
- 以太坊原始碼分析(43)node原始碼分析原始碼
- 以太坊原始碼分析(51)rpc原始碼分析原始碼RPC