【Java併發程式設計實戰】—– AQS(四):CLH同步佇列
在【Java併發程式設計實戰】—–“J.U.C”:CLH佇列鎖提過,AQS裡面的CLH佇列是CLH同步鎖的一種變形。其主要從兩方面進行了改造:節點的結構與節點等待機制。在結構上引入了頭結點和尾節點,他們分別指向佇列的頭和尾,嘗試獲取鎖、入佇列、釋放鎖等實現都與頭尾節點相關,並且每個節點都引入前驅節點和後後續節點的引用;在等待機制上由原來的自旋改成阻塞喚醒。其結構如下:
知道其結構了,我們再看看他的實現。線上程獲取鎖時會呼叫AQS的acquire()方法,該方法第一次嘗試獲取鎖如果失敗,會將該執行緒加入到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()的實現,在釐清這段程式碼之前我們要先看一個更重要的東東,Node,CLH佇列的節點。其原始碼如下:
static final class Node { /** 執行緒已被取消 */ static final int CANCELLED = 1; /** 當前執行緒的後繼執行緒需要被unpark(喚醒) */ static final int SIGNAL = -1; /** 執行緒(處在Condition休眠狀態)在等待Condition喚醒 */ static final int CONDITION = -2; /** 共享鎖 */ static final Node SHARED = new Node(); /** 獨佔鎖 */ static final Node EXCLUSIVE = null; volatile int waitStatus; /** 前繼節點 */ volatile Node prev; /** 後繼節點 */ volatile Node next; 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; } }
在這個原始碼中有三個值(CANCELLED、SIGNAL、CONDITION)要特別注意,前面提到過CLH佇列的節點都有一個狀態位,該狀態位與執行緒狀態密切相關:
CANCELLED = 1:因為超時或者中斷,節點會被設定為取消狀態,被取消的節點時不會參與到競爭中的,他會一直保持取消狀態不會轉變為其他狀態;
SIGNAL = -1:其後繼節點已經被阻塞了,到時需要進行喚醒操作;
CONDITION = -2:表示這個結點在條件佇列中,因為等待某個條件而被阻塞;
0:新建節點一般都為0。
入列
線上程嘗試獲取鎖的時候,如果失敗了需要將該執行緒加入到CLH佇列,入列中的主要流程是:tail執行新建node,然後將node的後繼節點指向舊tail值。注意在這個過程中有一個CAS操作,採用自旋方式直到成功為止。其程式碼如下:
for(;;){
Node t = tail;
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
其實這段程式碼在enq()方法中存在。
出列
當執行緒是否鎖時,需要進行“出列”,出列的主要工作則是喚醒其後繼節點(一般來說就是head節點),讓所有執行緒有序地進行下去:
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
取消
執行緒因為超時或者中斷涉及到取消的操作,如果某個節點被取消了,那個該節點就不會參與到鎖競爭當中,它會等待GC回收。取消的主要過程是將取消狀態的節點移除掉,移除的過程還是比較簡單的。先將其狀態設定為CANCELLED,然後將其前驅節點的pred執行其後繼節點,當然這個過程仍然會是一個CAS操作:
node.waitStatus = Node.CANCELLED;
Node pred = node.prev;
Node predNext = pred.next;
Node next = node.next;
掛起
我們瞭解了AQS的CLH佇列相比原始的CLH佇列鎖,它採用了一種變形操作,將自旋機制改為阻塞機制。當前執行緒將首先檢測是否為頭結點且嘗試獲取鎖,如果當前節點為頭結點併成功獲取鎖則直接返回,當前執行緒不進入阻塞,否則將當前執行緒阻塞:
for (;;) {
if (node.prev == head)
if(嘗試獲取鎖成功){
head=node;
node.next=null;
return;
}
阻塞執行緒
}
參考
相關推薦
【Java併發程式設計實戰】—– AQS(四):CLH同步佇列
在【Java併發程式設計實戰】—–“J.U.C”:CLH佇列鎖提過,AQS裡面的CLH佇列是CLH同步鎖的一種變形。其主要從兩方面進行了改造:節點的結構與節點等待機制。在結構上引入了頭結點和尾節點,他們
【java併發程式設計實戰】—–執行緒基本概念
轉自 http://cmsblogs.com/?p=1638 共享和可變 要編寫執行緒安全的程式碼,其核心在於對共享的和可變的狀態進行訪問。 “共享”就意味著變數可以被多個執行緒同時訪問。我們知道系統中的資源是有限的,不同的執行緒對資源都是具有著同等的使用權。有限、公平就意味著競爭
【Java併發程式設計實戰】—–synchronized
在我們的實際應用當中可能經常會遇到這樣一個場景:多個執行緒讀或者、寫相同的資料,訪問相同的檔案等等。對於這種情況如果我們不加以控制,是非常容易導致錯誤的。在java中,為了解決這個問題,引入臨界區概念。所謂臨界區是指一個訪問共用資源的程式片段,而這些共用資源又無法同時被多個執
【Java併發程式設計實戰】-----synchronized
在我們的實際應用當中可能經常會遇到這樣一個場景:多個執行緒讀或者、寫相同的資料,訪問相同的檔案等等。對於這種情況如果我們不加以控制,是非常容易導致錯誤的。在java中,為了解決這個問題,引入臨界區概念。所謂臨界區是指一個訪問共用資源的程式片段,而這些共用資源又無法同時被多個執行緒訪問。 在java中為了實現
【JAVA併發程式設計實戰】學習小結
第一章 簡介 摘書 執行緒會共享程序範圍內的資源,例如記憶體控制代碼和檔案控制代碼,但每個執行緒都有各自的程式計數器(Program Counter)、棧以及區域性變數等。 在同一個程式中的多
Java併發程式設計系列之十四:阻塞佇列
阻塞佇列(BlockingQueue)是一個支援兩個附加操作的佇列。這兩個附加操作支援阻塞地插入和移除方法。支援阻塞插入的方法是指當佇列滿時會阻塞插入元素的執行緒,直到佇列不滿;支援阻塞移除的方法是指當佇列為空時獲取元素的執行緒無法繼續獲取元素直到佇列不空。
【 專欄 】- Java併發程式設計實戰
其實我想要 一種美夢睡不著 一種心臟的狂跳 瓦解界線不被撂倒 奔跑 依靠 我心中最想要 看你看過的浪潮 陪你放肆地年少 ——林俊杰【偉大的渺小】 ------------
【Java併發程式設計】之二十:併發新特性—Lock鎖和條件變數(含程式碼)
簡單使用Lock鎖 Java 5中引入了新的鎖機制——java.util.concurrent.locks中的顯式的互斥鎖:Lock介面,它提供了比synchronized更加廣泛的鎖定操作。Lock介面有3個實現它的類:ReentrantLock、Reetrant
【Java併發程式設計】之一:可重入內建鎖
每個Java物件都可以用做一個實現同步的鎖,這些鎖被稱為內建鎖或監視器鎖。執行緒在進入同步程式碼塊之前會自動獲取鎖,並且在退出同步程式碼塊時會自動釋放鎖。獲得內建鎖的唯一途徑就是進入由這個鎖保護
【Java併發程式設計】之二十二:併發新特性—障礙器CyclicBarrier(含程式碼)
CyclicBarrier(又叫障礙器)同樣是Java 5中加入的新特性,使用時需要匯入java.util.concurrent.CylicBarrier。它適用於這樣一種情況:你希望建立一組任
【Java併發程式設計】之六:Runnable和Thread實現多執行緒的區別(含程式碼)
Java中實現多執行緒有兩種方法:繼承Thread類、實現Runnable介面,在程式開發中只要是多執行緒,肯定永遠以實現Runnable介面為主,因為實現Runnable介面相比繼承Th
【JAVA併發程式設計】--學習路線
學習JAVA併發程式設計,有一定的套路。我們需要關注的核心程式碼無非就是jdk下的併發工具包:java.util.concurrent,因此我們也可以在很多地方看到java併發程式設計簡稱為J.U.C程式設計。原始碼可以通過解壓縮jdk安裝目錄下的src.zip包檢視
【Java併發程式設計】深入分析ConcurrentHashMap(九)
本章是提高教程可能對於剛入門同學來說會有些難度,讀懂本章你需要了解以下知識點:一、Concurrent原始碼分析ConcurrentHashMap是由Segment(桶)、HashEntry(節點)2大資料結構組成。如下圖所示: 1.1 Segment類和屬性//Seg
【java併發程式設計】執行緒池原理分析及ThreadPoolExecutor原始碼實現
執行緒池簡介: 多執行緒技術主要解決處理器單元內多個執行緒執行的問題,它可以顯著減少處理器單元的閒置時間,增加處理器單元的吞吐能力。 假設一個伺服器完成一項任務所需時間為:T1 建立執行緒時間,T2 線上程中執行任務的時間,T3 銷燬執行緒時間。
【Java併發程式設計】之二十三:併發新特性—訊號量Semaphore(含程式碼)
在作業系統中,訊號量是個很重要的概念,它在控制程序間的協作方面有著非常重要的作用,通過對訊號量的不同操作,可以分別實現程序間的互斥與同步。當然它也可以用於多執行緒的控制,我們完全可以通過
【JAVA併發程式設計】--synchronized應用及解析
相信大多數同學在開始接觸併發程式設計的時候,首先了解的就是synchronized關鍵字的修飾,被synchronized修飾的方法或程式碼塊都可以解決多執行緒安全問題。在Java SE1.6版本之前,我們稱之為重量級鎖。因為它在獲取共享鎖的時候是對CPU的獨
【JAVA併發程式設計】--為什麼要學習JAVA併發?
我們常常在學習一門新技術之前,都要問自己一遍:為什麼要學習這門技術? 就如當年你是為何投入JAVA的大軍,而非C++\PHP\Phython?拿我自己來講,想法尤其簡單。因為那時JAVA最火啊,用這門技術的企業最多,工作最好找。 哈哈,我相信這
【Java併發程式設計】之十六:深入Java記憶體模型——happen-before規則及其對DCL的分析(含程式碼)
happen—before規則介紹 Java語言中有一個“先行發生”(happen—before)的規則,它是Java記憶體模型中定義的兩項操作之間的偏序關係,如果操作A先行發生於操作B,其意思就是說,在發生操作B之前,操作A產生的影響都能被操作B觀察到,“影響
【Java併發程式設計】深入分析Thread(七)
一、執行緒 1.1什麼是執行緒? 執行緒,有時被稱為輕量級程序(Lightweight Process,LWP),是程式執行流的最小單元。一個標準的執行緒由執行緒ID,當前指令指標(PC),暫存器集合和堆疊組成。另外,執行緒是程序中的一個實體,是被系統獨
【Java併發程式設計】之八:多執行緒環境中安全使用集合API(含程式碼)
在集合API中,最初設計的Vector和Hashtable是多執行緒安全的。例如:對於Vector來說,用來新增和刪除元素的方法是同步的。如果只有一個執行緒與Vector的例項互動,那麼,要求獲取