您好,登錄后才能下訂單哦!
小編給大家分享一下Java并發(fā)J.U.C之AQS:CLH同步隊列的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
CLH同步隊列是一個FIFO雙向隊列,AQS依賴它來完成同步狀態(tài)的管理,當(dāng)前線程如果獲取同步狀態(tài)失敗時,AQS則會將當(dāng)前線程已經(jīng)等待狀態(tài)等信息構(gòu)造成一個節(jié)點(Node)并將其加入到CLH同步隊列,同時會阻塞當(dāng)前線程,當(dāng)同步狀態(tài)釋放時,會把首節(jié)點喚醒(公平鎖),使其再次嘗試獲取同步狀態(tài)。
在CLH同步隊列中,一個節(jié)點表示一個線程,它保存著線程的引用(thread)、狀態(tài)(waitStatus)、前驅(qū)節(jié)點(prev)、后繼節(jié)點(next),其定義如下:
static final class Node { /** 共享 */ static final Node SHARED = new Node(); /** 獨占 */ static final Node EXCLUSIVE = null; /** * 因為超時或者中斷,節(jié)點會被設(shè)置為取消狀態(tài),被取消的節(jié)點時不會參與到競爭中的,他會一直保持取消狀態(tài)不會轉(zhuǎn)變?yōu)槠渌麪顟B(tài); */ static final int CANCELLED = 1; /** * 后繼節(jié)點的線程處于等待狀態(tài),而當(dāng)前節(jié)點的線程如果釋放了同步狀態(tài)或者被取消,將會通知后繼節(jié)點,使后繼節(jié)點的線程得以運行 */ static final int SIGNAL = -1; /** * 節(jié)點在等待隊列中,節(jié)點線程等待在Condition上,當(dāng)其他線程對Condition調(diào)用了signal()后,改節(jié)點將會從等待隊列中轉(zhuǎn)移到同步隊列中,加入到同步狀態(tài)的獲取中 */ static final int CONDITION = -2; /** * 表示下一次共享式同步狀態(tài)獲取將會無條件地傳播下去 */ static final int PROPAGATE = -3; /** 等待狀態(tài) */ volatile int waitStatus; /** 前驅(qū)節(jié)點 */ volatile Node prev; /** 后繼節(jié)點 */ volatile Node next; /** 獲取同步狀態(tài)的線程 */ volatile Thread thread; Node 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) { this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { this.waitStatus = waitStatus; this.thread = thread; } }
CLH同步隊列結(jié)構(gòu)圖如下:
入列
學(xué)了數(shù)據(jù)結(jié)構(gòu)的我們,CLH隊列入列是再簡單不過了,無非就是tail指向新節(jié)點、新節(jié)點的prev指向當(dāng)前最后的節(jié)點,當(dāng)前最后一個節(jié)點的next指向當(dāng)前節(jié)點。代碼我們可以看看addWaiter(Node node)方法:
private Node addWaiter(Node mode) { //新建Node Node node = new Node(Thread.currentThread(), mode); //快速嘗試添加尾節(jié)點 Node pred = tail; if (pred != null) { node.prev = pred; //CAS設(shè)置尾節(jié)點 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //多次嘗試 enq(node); return node; }
addWaiter(Node node)先通過快速嘗試設(shè)置尾節(jié)點,如果失敗,則調(diào)用enq(Node node)方法設(shè)置尾節(jié)點
private Node enq(final Node node) { //多次嘗試,直到成功為止 for (;;) { Node t = tail; //tail不存在,設(shè)置為首節(jié)點 if (t == null) { if (compareAndSetHead(new Node())) tail = head; } else { //設(shè)置為尾節(jié)點 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
在上面代碼中,兩個方法都是通過一個CAS方法compareAndSetTail(Node expect, Node update)來設(shè)置尾節(jié)點,該方法可以確保節(jié)點是線程安全添加的。在enq(Node node)方法中,AQS通過“死循環(huán)”的方式來保證節(jié)點可以正確添加,只有成功添加后,當(dāng)前線程才會從該方法返回,否則會一直執(zhí)行下去。
過程圖如下:
出列
CLH同步隊列遵循FIFO,首節(jié)點的線程釋放同步狀態(tài)后,將會喚醒它的后繼節(jié)點(next),而后繼節(jié)點將會在獲取同步狀態(tài)成功時將自己設(shè)置為首節(jié)點,這個過程非常簡單,head執(zhí)行該節(jié)點并斷開原首節(jié)點的next和當(dāng)前節(jié)點的prev即可,注意在這個過程是不需要使用CAS來保證的,因為只有一個線程能夠成功獲取到同步狀態(tài)。過程圖如下:
以上是“Java并發(fā)J.U.C之AQS:CLH同步隊列的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。