1 同步隊列(CLH)
1.1 簡介
CLH隊列是Craig, Landin, and Hagersten三人發明的一種基於雙向鏈表數據結構的隊列。是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程僅僅在本地變量上自旋,它不斷輪詢前驅的狀態,假設發現前驅釋放了鎖就結束自旋。
JAVA中的CLH隊列是原CLH隊列的一個變種,線程由原來的自旋機制改爲阻塞機制。
1.2 Node結構
static final class Node {
/**################節點模式##################*/
/** 共享*/
static final Node SHARED = new Node();
/** 獨佔 */
static final Node EXCLUSIVE = null;
/**
* CANCELLED:等待線程超時或者被中斷、需要從同步隊列中取消等待(也就是放棄資源的競爭)
* SIGNAL:後繼節點會處於等待狀態,當前節點線程如果釋放同步狀態或者被取消則會通知後繼節點線程,使後繼節點線程的得以運行
* CONDITION:節點在等待隊列中,線程在等待在Condition 上,其他線程對Condition調用singnal()方法後,該節點加入到同步隊列中。
* PROPAGATE:表示下一次共享式獲取同步狀態的時會被無條件的傳播下去。
*/
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
/**等待狀態*/
volatile int waitStatus;
/**前指針*/
volatile Node prev;
/**後指針*/
volatile Node next;
/**線程*/
volatile Thread thread;
/**
* AQS中條件隊列是使用單向列表保存的,用nextWaiter來連接。
*/
Node nextWaiter;
/** 判斷nextWaiter是否爲共享*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**獲取當前節點的前驅節點 */
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
/**
* ######################################
* ##############構造方法################
* ######################################
*/
Node() {
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
2 state 同步狀態
state 的作用,它爲 0 的時候代表沒有線程佔有鎖,可以去爭搶這個鎖,用 CAS 將 state 設爲 1,如果 CAS 成功,說明搶到了鎖,這樣其他線程就搶不到了,如果鎖重入的話,state進行 +1 就可以,解鎖就是減 1,直到 state 又變爲 0,代表釋放鎖,所以 lock() 和 unlock() 必須要配對啊。然後喚醒等待隊列中的第一個線程,讓其來佔有鎖。
/**
* 得到同步狀態
*/
protected final int getState() {
return state;
}
/**
* 設置同步狀態
*/
protected final void setState(int newState) {
state = newState;
}
/**
* 利用unsafe的cas設置同步狀態(原子操作)
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
3 資源
AQS定義兩種資源共享方式:Exclusive(獨佔,只有一個線程能執行,如ReentrantLock)和Share(共享,多個線程可同時執行,如Semaphore/CountDownLatch)。不同的自定義同步器爭用共享資源的方式也不同。自定義同步器在實現時只需要實現共享資源state的獲取與釋放方式即可,至於具體線程等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實現好了。自定義同步器實現時主要實現以下幾種方法:
(1) tryAcquire(int):獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。
(2) tryRelease(int):獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。
(3) tryAcquireShared(int):共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。
(4) tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點返回true,否則返回false。
4 addWaiter
該方法用於將當前線程根據不同的模式加入到等待隊列的隊尾,並返回當前線程所在的結點。
4.1 addWaiter
private Node addWaiter(Node mode) {
//1 創建一個節點
Node node = new Node(Thread.currentThread(), mode);
//2 獲取隊尾,隊尾不爲空
Node pred = tail;
if (pred != null) {
//2.1 設置新節點的前驅是隊尾
node.prev = pred;
//2.2 cas快速設置入隊尾(expect=pred,update=tail)
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//3 自旋設置隊尾
enq(node);
return node;
}
4.2 enq
private Node enq(final Node node) {
//自旋
for (;;) {
//獲取隊尾
Node t = tail;
//如果隊尾爲空
if (t == null) {
//創建隊頭、隊尾
if (compareAndSetHead(new Node()))
tail = head;
} else {
//隊尾的前驅節點是隊尾
node.prev = t;
//cas 設置隊尾(expect=tail,update=node)
if (compareAndSetTail(t, node)) {
//把原來隊尾的後驅節點設置爲更新後的隊尾節點
t.next = node;
return t;
}
}
}
}
5 acquire
/**
* 以獨佔、忽略打斷的方式獲取資源
*/
public final void acquire(int arg) {
// 嘗試獲取資源,入隊尾,獲取資源或進入等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
5.1 acquireQueued
/*
* 獲取資源或進入等待
*/
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);
//把隊頭的後指針設置爲null,help GC
p.next = null;
failed = false;
return interrupted;
}
//判斷是否進入等待
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//如果沒有搶到資源,取消當前節點獲取資源
if (failed)
cancelAcquire(node);
}
}
5.2 shouldParkAfterFailedAcquire
/**
* 是否能夠進入等待狀態
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//pred 節點的狀態
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* SIGNAL狀態,當前節點直接進入等待狀態,返回結果爲true
*/
return true;
if (ws > 0) {
/*
* 只有CANCELLED狀態大於0
* pred爲CANCELLED狀態,及無效狀態,爲當前node找到有效的前驅pred
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//更新前驅節點的後指針
pred.next = node;
} else {
/*
* cas設置前驅節點爲SIGNAL狀態
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
/**
* 如果前驅節點不爲SIGNAL,返回false,再次進入acquireQueued的自旋
*/
return false;
}
5.3 parkAndCheckInterrupt
/**
* 使用LockSupport進入等待,LockSupport調用unsafe方法,unsafe爲java調用C
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
6 release
6.1 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;
}
6.2 unparkSuccessor
/*
* 喚醒等待節點
*/
private void unparkSuccessor(Node node) {
//獲取當前節點狀態
int ws = node.waitStatus;
//如果狀態小於0,初始化狀態
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//獲取當前節點的後繼節點
Node s = node.next;
//後繼節點爲空或後繼節點的狀態爲CANCELLED狀態
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);
}