源碼分析AbstractQuenedSynchronized(二)
源碼分析AbstractQuenedSynchronized(三)
文章目錄
從 ReentrantLock 的公平鎖源碼出發,分析AbstractQueuedSynchronizer 這個類如何工作
AbstractQuenedSynchronized
該類是一個抽象類,簡稱AQS,是Java併發包的基礎工具類,是實現ReentrantLock、CountDownLatch、Semaphore、FutureTask等類的基礎。
數據結構
雙向隊列,結點Node包含:pre+next+thread+waitStatus
圖片來源https://javadoop.com/post/AbstractQueuedSynchronizer
static final class Node {
//標識結點在共享模式下
static final Node SHARED = new Node();
//標識結點在獨佔模式下
static final Node EXCLUSIVE = null;
//================WaitStatus的狀態==================//
static final int CANCELLED = 1;//取消爭搶鎖
static final int SIGNAL = -1;//當前node的後繼結點對應的線程需要被喚醒
static final int CONDITION = -2;//結點在等待隊列中,結點線程等待在Condition上
static final int PROPAGATE = -3;//下一次共享式同步狀態獲取將會無條件地被傳播下去
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;//用於條件隊列
}
模板方法
//這些模板方法的作用體現在下面對ReentrantLock的分析中
void acquire(int arg) ***->子類會覆寫其中的boolean tryAcquire(arg)方法
boolean release(int arg) ***->子類會覆寫其中的boolean tryRelease(arg)方法
Node addWaiter(Node mode)//循環CAS操作將結點加入同步隊列尾部
Node enq(final Node node)//addWaiter中的方法
boolean acquireQueued(final Node node, int arg)//循環CAS操作更新同步隊列
boolean shouldParkAfterFailedAcquire()//判斷是否需要被阻塞
boolean parkAndCheckInterrupt()//阻塞當前線程然後判斷線程等待狀態時是否被中斷
void unparkSuccessor(Node node)//喚醒後繼結點
ReentrantLock
默認非公平鎖,通過構造器參數中的布爾值來確定鎖是否公平,通過內部類Sync來管理鎖,真正的獲取鎖和釋放鎖由Sync兩個實現類來實現,
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock獲得公平鎖
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//爭鎖
final void lock() {
acquire(1);
}
/*========調用父類AQS的acquire方法,注意這裏面涉及到3個方法:tryAcquire()【子類覆寫父類AQS】、addWaiter()【父類AQS】、acquireQueued()【父類AQS】,下面一一講解==========
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//調用成功則不需要排隊
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//否則將該線程包裝成結點添加到同步隊列中。Node.EXCLUSIVE=null;
selfInterrupt();
}
*/
/**
* 覆寫了父類AQS的tryAcquire方法
* 兩種情況下說明嘗試獲得鎖成功,1.沒有線程持有鎖,也就是沒有線程在等待鎖 2.進行鎖重入
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//獲取等待狀態
//1.state==0,沒有線程持有鎖
if (c == 0) {
if (!hasQueuedPredecessors() &&//沒有線程在等待鎖
//嘗試CAS操作獲取鎖
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);//獲取成功則標記當前線程已持有鎖
return true;
}
}
//2.該分支說明鎖可重入了,state=state+1;
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
/* ==================調用父類AQS的addWaiter方法,該方法的功能是通過循環CAS將新的結點加入到同步隊列中,這裏面又有一個enq()方法====================
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;
if (compareAndSetTail(pred, node)) {//如果CAS操作將新的結點加入到隊列末尾則直接返回該結點
//進入到這裏說明設置成功,tail==node
pred.next = node;
return node;
}
}
//執行到這裏說明:要麼加入該結點前隊列爲空;要麼CAS操作沒有成功
enq(node);
return node;
}
*/
/*======================父類AQS的enq方法===============================
* 進入到這個方法只有兩種可能:1.隊列爲空 2.有線程競爭入隊
* 採用自旋方式入隊:CAS設置tail過程中,競爭一次競爭不到,就多次競爭,總會排到的
* @return node's predecessor
private Node enq(final Node node) {
//循環CAS
for (;;) {
Node t = tail;
if (t == null) { // Must initialize 初始時隊列爲空,head和tail都爲空,因此第一個結點加入隊列後肯定會進入到該方法進行初始化
if (compareAndSetHead(new Node()))
tail = head;//新實例化一個Node對象作爲頭結點,這個時候head.waitStatus==0,表示初始狀態,
//!!!!!!!!!!注意沒有return!!!!!!!!!!!!!! 繼續for循環!讓多線程從head結點後面開始排隊
} else {
//下一個線程進來向獲取鎖,發現同步隊列中尾結點不爲空,就會插入到隊列尾進行排隊。當多個線程同時進來競爭時,通過CAS循環,將排隊操作串行化
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
*/
/*========================父類AQS的方法acquireQueued,該方法的作用是循環CAS操作不斷嘗試去更新同步隊列的head,即不斷嘗試去獲得鎖=======================================
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;//標記是否成功獲取同步狀態,初始時設置爲failed==true,即未獲取同步狀態
try {
boolean interrupted = false;//標記等待過程中是否被中斷過
for (;;) {
final Node p = node.predecessor();//p是node的前驅結點
//如果p是head,也就是說node是阻塞隊列的第一個結點,則嘗試獲取同步狀態
//注意:我們認爲阻塞隊列不包含head結點,head一般指的是佔有同步狀態的線程,head後面的才稱爲阻塞隊列
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;//成功獲取同步狀態
return interrupted;
}
//代碼執行到這裏,說明node嘗試獲取鎖失敗,原因有兩個:1. node不是隊頭 2.與其他線程爭取同步狀態失敗
if (shouldParkAfterFailedAcquire(p, node) //判斷當前線程是否需要被掛起,需要掛起則返回true
&&
parkAndCheckInterrupt())//執行當前線程的掛起,調用LockSupport的park方法,等待被喚醒
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
*/
/*============================父類AQS的shouldParkAfterFailedAcquire()方法==========================
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//前驅結點狀態爲signal,則當前結點可以被掛起,等待前驅結點釋放同步狀態或者被中斷
if (ws == Node.SIGNAL)
return true;
//前驅結點狀態>0,則說明前驅結點取消了排隊,因此繼續往前找,直到找到一個狀態<=0的結點作爲前驅結點
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 進入這個分支意味着等待狀態爲0,-2,-3
// 由於之前都沒有設置waitStatus,所以每個新的node入隊時,waitStatus都是0
// 正常情況下前驅結點是之前的tail,它的waitStatus應該是0
// CAS操作將前驅結點的waitStatus設置爲Node.Signal(也就是-1)
// 然後從該方法中返回false,進入上層方法的for循環,又重新進入該方法從從一個分支返回true
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
*/
/*===================父類AQS的parkAndCheckInterrupt()方法=============================
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//利用LockSupport工具阻塞當前線程,只有調用unpark()或者當前線程被中斷才能夠從park()方法返回
return Thread.interrupted();
}
*/
}
ReentrantLock釋放公平鎖
public void unlock() {
sync.release(1);
/*=================父類AQS的release()方法。裏面又有tryRelease()方法【子類Sync覆寫】和unparkSuccessor()方法=====================
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
*/
/*====================Sync類的tryRelease()方法,重寫了父類AQS的tryRelease方法==================
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//c==0說明完全釋放鎖
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
*/
/*============================父類AQS的unparkSuccessor()方法==========================
private void unparkSuccessor(Node node) {//此處node爲頭結點head
int ws = node.waitStatus;
if (ws < 0)//頭結點waitStatus小於0則將其設爲0
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;//s爲同步隊列中的第一個結點
//如果s爲空或者狀態大於0即取消了等待,那麼從隊列尾部向前尋找waitStatus<0結點中排在最前面的
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;
}
//喚醒後繼結點,後繼結點在acquireQueued方法中通過for循環繼續執行,發現前繼結點是head從而獲取鎖
if (s != null)
LockSupport.unpark(s.thread);
}
*/
}
公平鎖和非公平鎖
公平鎖的爭鎖過程
static final class FairSync extends Sync {
//爭鎖
final void lock() {
acquire(1);
}
/*========調用父類AQS的acquire方法,注意這裏面涉及到3個方法:tryAcquire()【子類覆寫父類AQS】、addWaiter()【父類AQS】、acquireQueued()【父類AQS】==========
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//調用成功則不需要排隊
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//否則將該線程包裝成結點添加到同步隊列中。Node.EXCLUSIVE=null;
selfInterrupt();
}
*/
/**
* 覆寫了父類AQS的tryAcquire方法
* 兩種情況下說明嘗試獲得鎖成功,1.沒有線程持有鎖,也就是沒有線程在等待鎖 2.進行鎖重入
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//獲取等待狀態
//1.state==0,沒有線程持有鎖
if (c == 0) { 【對比】非公平鎖在此處沒有對等待線程的判斷
if (!hasQueuedPredecessors() &&//沒有線程在等待鎖
//嘗試CAS操作獲取鎖
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);//獲取成功則標記當前線程已持有鎖
return true;
}
}
//2.該分支說明鎖可重入了,state=state+1;
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
非公平鎖的爭鎖過程
static final class NonfairSync extends Sync {
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))【對比】和公平鎖相比,並沒有對鎖的等待狀態進行判斷,而是直接調用一次CAS,嘗試去獲得鎖
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
/*========調用父類AQS的acquire方法,注意這裏面涉及到3個方法:tryAcquire()【子類覆寫父類AQS】、addWaiter()【父類AQS】、acquireQueued()【父類AQS】==========
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//調用成功則不需要排隊
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//否則將該線程包裝成結點添加到同步隊列中。Node.EXCLUSIVE=null;
selfInterrupt();
}
*/
protected final boolean tryAcquire(int acquires) {
return 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;
}
}
總結
非公平鎖與公平鎖的lock方法有以下兩點不同:
- 非公平鎖: 進入lock方法後直接嘗試CAS獲得鎖,成功則直接返回;
公平鎖: 進入lock方法後首先要獲取鎖的等待狀態state,判斷是否有線程在等待鎖(state是否爲0)
- 非公平鎖: CAS未成功後進入父類AQS的acquire方法,然後調用自己的nonfairTryAcquire方法,如果暫時沒有線程持有鎖或者當前鎖可重入,則直接獲得鎖,也不會對阻塞隊列有沒有線程在等待鎖作判斷,如果沒有獲得鎖則後續步驟和公平鎖一樣,將當前線程封裝成結點插入到隊列中自旋等待。
公平鎖: 在步驟1的基礎上,當鎖的等待狀態爲0,即未有線程持有鎖時,會進而判斷阻塞隊列中有沒有線程在等待鎖
對比之下,非公平鎖的吞吐量更大,但是也有可能使阻塞隊列中的線程長期處於飢餓狀態。