aqs clh java_【Java并发编程实战】—– AQS(四):CLH同步队列
在【Java并發(fā)編程實戰(zhàn)】—–“J.U.C”:CLH隊列鎖提過,AQS里面的CLH隊列是CLH同步鎖的一種變形。
其主要從雙方面進行了改造:節(jié)點的結(jié)構(gòu)與節(jié)點等待機制。在結(jié)構(gòu)上引入了頭結(jié)點和尾節(jié)點,他們分別指向隊列的頭和尾,嘗試獲取鎖、入隊列、釋放鎖等實現(xiàn)都與頭尾節(jié)點相關(guān)。而且每一個節(jié)點都引入前驅(qū)節(jié)點和后興許節(jié)點的引用;在等待機制上由原來的自旋改成堵塞喚醒。
其結(jié)構(gòu)例如以下:
知道其結(jié)構(gòu)了,我們再看看他的實現(xiàn)。在線程獲取鎖時會調(diào)用AQS的acquire()方法。該方法第一次嘗試獲取鎖假設(shè)失敗,會將該線程增加到CLH隊列中:public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter:private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
這是addWaiter()的實現(xiàn),在厘清這段代碼之前我們要先看一個更重要的東東,Node,CLH隊列的節(jié)點。
其源代碼例如以下:static final class Node {
/** 線程已被取消 */
static final int CANCELLED = 1;
/** 當(dāng)前線程的后繼線程須要被unpark(喚醒) */
static final int SIGNAL = -1;
/** 線程(處在Condition休眠狀態(tài))在等待Condition喚醒 */
static final int CONDITION = -2;
/** 共享鎖 */
static final Node SHARED = new Node();
/** 獨占鎖 */
static final Node EXCLUSIVE = null;
volatile int waitStatus;
/** 前繼節(jié)點 */
volatile Node prev;
/** 后繼節(jié)點 */
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
/** 獲取前繼節(jié)點 */
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
/**
* 三個構(gòu)造函數(shù)
*/
Node() {
}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
在這個源代碼中有三個值(CANCELLED、SIGNAL、CONDITION)要特別注意,前面提到過CLH隊列的節(jié)點都有一個狀態(tài)位,該狀態(tài)位與線程狀態(tài)密切相關(guān):
CANCELLED =? 1:由于超時或者中斷,節(jié)點會被設(shè)置為取消狀態(tài),被取消的節(jié)點時不會參與到競爭中的,他會一直保持取消狀態(tài)不會轉(zhuǎn)變?yōu)槠渌鼱顟B(tài)。
SIGNAL??? = -1:其后繼節(jié)點已經(jīng)被堵塞了,到時須要進行喚醒操作;
CONDITION = -2:表示這個結(jié)點在條件隊列中,由于等待某個條件而被堵塞;
0:新建節(jié)點一般都為0。
入列
在線程嘗試獲取鎖的時候,假設(shè)失敗了須要將該線程增加到CLH隊列,入列中的主要流程是:tail運行新建node,然后將node的后繼節(jié)點指向舊tail值。
注意在這個過程中有一個CAS操作,採用自旋方式直到成功為止。其代碼例如以下:for(;;){
Node t = tail;
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
事實上這段代碼在enq()方法中存在。
出列
當(dāng)線程是否鎖時,須要進行“出列”。出列的主要工作則是喚醒其后繼節(jié)點(一般來說就是head節(jié)點),讓全部線程有序地進行下去:Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
取消
線程由于超時或者中斷涉及到取消的操作,假設(shè)某個節(jié)點被取消了。那個該節(jié)點就不會參與到鎖競爭其中,它會等待GC回收。取消的主要過程是將取消狀態(tài)的節(jié)點移除掉,移除的過程還是比較簡單的。先將其狀態(tài)設(shè)置為CANCELLED,然后將其前驅(qū)節(jié)點的pred運行其后繼節(jié)點。當(dāng)然這個過程仍然會是一個CAS操作:node.waitStatus = Node.CANCELLED;
Node pred = node.prev;
Node predNext = pred.next;
Node next = node.next;
掛起
我們了解了AQS的CLH隊列相比原始的CLH隊列鎖,它採用了一種變形操作。將自旋機制改為堵塞機制。
當(dāng)前線程將首先檢測是否為頭結(jié)點且嘗試獲取鎖,假設(shè)當(dāng)前節(jié)點為頭結(jié)點并成功獲取鎖則直接返回。當(dāng)前線程不進入堵塞,否則將當(dāng)前線程堵塞:for (;;) {
if (node.prev == head)
if(嘗試獲取鎖成功){
head=node;
node.next=null;
return;
}
堵塞線程
}
參考
總結(jié)
以上是生活随笔為你收集整理的aqs clh java_【Java并发编程实战】—– AQS(四):CLH同步队列的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java源码导入eclipse_spri
- 下一篇: java使用d3_[Java教程]一个初