模板方法
這個隊列同步器的設計就是基於模板方法
模板設計模式的定義:定義一個操作中的算法的框架,而將一些步驟延遲到子類中,使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。
大致介紹
同步器依賴內部的同步隊列(一個FIFO雙向隊列),來完成同步狀態的管理,當前線程獲取同步狀態失敗時,同步器會將當前線程以及等待信息放入到一個節點Node並將其加入同步隊列,同時會阻塞當前線程,但同步狀態釋放時,會將首節點的線程喚醒,使其產生獲取同步狀態.
同步隊列的節點(Node)用來保存獲取同步狀態失敗的線程引用,等待狀態以及前驅和後驅節點,結點的屬性類型與名稱以及描述.
static final class Node {
//等待狀態
volatile int waitStatus;
//前驅
volatile Node prev;
//後驅
volatile Node next;
//獲取同步狀態失敗的線程引用
volatile Thread thread;
Node nextWaiter;
}
節點是構成同步隊列的基礎,同步器擁有首節點和尾結點,沒有成功獲取同步狀態的線程將會加入該隊列的尾部.
試想一下,一個線程獲取同步狀態成功,其他線程無法獲取同步狀態,轉而去構造結點並加入同步隊列,而加入隊列必須是線程安全的(因爲這個可能是競爭的,很多線程沒獲取到,然後競爭加入隊列).同步器提供了基於CAS的設置尾結點的方法:compareAndSetTail(Node except,Node Update)
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
上面這個是某個重寫tryAcquire(int arg)
方法的.
同步隊列遵循FIFO.
獨佔式
獲取同步狀態
通過調用同步器的acquire(int arg)方法可以獲取同步狀態,該方法對中斷不敏感,也就是由於線程獲取同步狀態失敗後進入同步隊列,後續對線程進行中斷操作時,線程不會從同步隊列中移除
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//該方法就是創建一個節點,然後試圖去創建一個雙向鏈表的尾結點
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//此處可以看到,它在創建成功後會立刻嘗試去加入尾結點,如果失敗就會進入下面的enq的for循環裏面
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//如果首節點不存在,就創建它,
if (compareAndSetHead(new Node()))
tail = head;
} else {
//此處就是for循環插入爲節點,CAS式必將並設置
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
/**
* Convenience method to interrupt current thread.
阻塞當前線程
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//當前結點的前驅結點p,
final Node p = node.predecessor();
//如果p是頭結點,並且嘗試獲取
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);
}
}
private void setHead(Node node) {
//賦給頭結點
head = node;
node.thread = null;
node.prev = null;
}
上述代碼主要邏輯是:首先調用自定義同步器實現的tryAcquire(int arg)
方法,該方法保證線程安全的獲取同步狀態,如果同步狀態獲取失敗就構造一個同步節點(獨佔式的Node.EXCLUSIVE,同一時刻只有一個線程獲取同步狀態成功)並通過addWriter(Node node)方法將該結點加入同步隊列尾部,最後調用acquireQueued(Node node,int arg),使得該結點以死循環方式獲取通過狀態.最後,如果獲取不到同步狀態,就阻塞結點中的線程,而被阻塞線程的喚醒主要依靠前驅結點的出隊或阻塞線程被中斷來實現.可以看出,enq(final Node node)方法將併發添加及誒到哪 的請求通過CAS變得""串行化
由於非首節點線程前驅結點出隊或者被中斷而從等待狀態返回,隨後檢查自己的前驅是否是頭結點,如果是就嘗試獲取同步狀態.可以看出,節點和節點之間在循環檢查中是互相不通信的,而正是檢查自己的前驅是否是頭結點,這樣就可以使得節點的釋放規則符合FIFO,並且也便於對過早通知的處理(過早通知是指前驅節點不是頭結點的線程由於終端被喚醒)
同步器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;
}
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);
}
當前線程獲取同步狀態並執行相應邏輯之後,就需要釋放同步方法,使得後續節點能夠繼續獲取同步狀態.通過調用同步器的relase(int arg)
方法可以釋放同步狀態,該方法在釋放了同步狀態之後,會喚醒其後續節點.
分析了獨佔式同步狀態獲取和釋放過程後,適當做個總結:在獲取同步狀態時,同步器維護一個同步隊列,獲取狀態失敗的線程都會被加入到隊列中並在隊列中進行自旋;移出隊列(或停止自旋)的條件是前驅節點爲頭節點且成功獲取了同步狀態。在釋放同步狀態時,同步器調用tryRelease(int arg)
方法釋放同步狀態,然後喚醒頭節點的後繼節點。
共享式
獲取同步狀態
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
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;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在acquireShared(int arg)
方法中,同步器調用tryAcquireShared(int arg)方法嘗試獲取同步狀態,tryAcquireShared(int arg)
返回值是int類型,但返回值大於0就代表可以獲取同步狀態。因此在共享式獲取的自旋過程中,成功獲取到同步狀態並退出自旋的條件就是tryAcquireShared(int arg)大於等於0
釋放同步狀態
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
釋放同步狀態一般是通過循環和cas來保證,因爲釋放同步狀態的操作會同時來自多個線程。