概述
AQS是AbstractQueuedSynchronizer的縮寫,翻譯成中文就是抽象隊列同步器,AbstractQueuedSynchronizer這個類也是在java.util.concurrent.locks下面。簡單來說AQS定義了一套多線程訪問共享資源的同步器框架,這套框架定義了共同的基礎行為,比如等待隊列、條件隊列、獨占獲取、共享獲取等,AQS也是一個依賴狀態state的同步器,而且java並發編程的核心包java.concurrent.util都需要這套框架。比如Lock,Latch,Barrier等,都是基於AQS框架實現。
我們在學習一套並發工具的時候,我們首先要抓住這3點:
狀態:一般是一個state屬性,它基本是整個工具的核心,通常整個工具都是在設置和修改狀態,很多方法的操作都依賴於當前狀態是什么。由於狀態是全局共享的,一般會被設置成volatile類型,以保證其修改的可見性。
隊列:隊列通常是一個等待對象 Node 的集合,大多數以鏈表的形式實現。隊列采用的是悲觀鎖的思想,表示當前所等待的資源,狀態或者條件短時間內可能無法滿足。因此,它會將當前線程包裝成某種 類型的數據結構 Node ,放入一個等待隊列中,當一定條件滿足后,再從等待隊列中取出。
CAS:CAS操作是最輕量的並發處理,通常我們對於狀態的修改都會用到CAS操作,因為狀態可能被多個線程同時修改,CAS操作保證了同一個時刻,只有一個線程 能修改成功,從而保證了線程安全,CAS操作基本是由Unsafe工具類的compareAndSwapXXX來實現的;CAS采用的是樂觀鎖的思想,因 此常常伴隨着自旋,如果發現當前無法成功地執行CAS,則不斷重試,直到成功為止,自旋的的表現形式通常是一個死循環for(;;)。
AQS具備特性
特點:1,阻塞等待隊列;2,共享/獨占;3,公平/非公平;4,可重入;5,允許中斷。
first-in-first-out (FIFO) wait queues
blocking locks and related synchronizers (semaphores, events, etc)
樂觀鎖
共享鎖shared是一個樂觀鎖。可以允許多個線程阻塞點,可以多個線程同時獲取到鎖。它允許一個資源可以被多個讀操作,或者被一個寫操作訪問,但是兩個操作不能同時訪問。
Java中的樂觀鎖基本都是通過CAS操作實現的,CAS是一種更新的原子操作,比較當前值跟傳入值版本號是否一樣,一樣的更新,否則失敗。
悲觀鎖
獨占鎖exclusive是一個悲觀鎖。保證只有一個線程經過一個阻塞點,只有一個線程可以獲得鎖。
Java中的悲觀鎖就是synchronized,AQS框架下的鎖則是先嘗試CAS樂觀鎖去獲取,獲取不到才會轉為悲觀鎖,如ReentrantLock
大量使用了CAS操作, 並且在沖突時,采用自旋方式重試,以實現輕量級和高效地獲取鎖。
AQS可以實現獨占鎖和共享鎖
通 過一個CLH隊列實現的(CLH鎖即Craig, Landin, and Hagersten (CLH) locks,CLH鎖是一個自旋鎖,能確保無飢餓性,提供先來先服務的公平性。CLH鎖也是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本 地變量上自旋,它不斷輪詢前驅的狀態,如果發現前驅釋放了鎖就結束自旋。)
AQS框架
AbstractQueuedSynchronizer是JDK實現其他同步工具的基礎。
AQS內部封裝了一個狀態volatile int state用來表示資源,提供了獨占以及共享兩種操作:acquire(acquireShare)/release(releaseShare)。
acquire的語義是:獲取資源,如果當前資源滿足條件,則直接返回,否則掛起當前線程
release的語義是:釋放資源,喚醒掛起線程
它維護了一個volatile int state(代表共享資源)和一個FIFO線程等待隊列(多線程爭用資源被阻塞時會進入此隊列)。這里volatile是核心關鍵詞,具體volatile的語義,在此不述。state的訪問方式有三種:getState()、setState()、compareAndSetState()
AQS定義兩種資源共享方式:Exclusive(獨占,只有一個線程能執行,如ReentrantLock)和Share(共享,多個線程可同時執行,如Semaphore/CountDownLatch)。
不同的自定義同步器爭用共享資源的方式也不同。自定義同步器在實現時只需要實現共享資源state的獲取與釋放方式即可,至於具體線程等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實現好了。自定義同步器實現時主要實現以下幾種方法:
isHeldExclusively():該線程是否正在獨占資源。只有用到condition才需要去實現它。
tryAcquire(int):獨占方式。嘗試獲取資源,成功則返回true,失敗則返回false。
tryRelease(int):獨占方式。嘗試釋放資源,成功則返回true,失敗則返回false。
tryAcquireShared(int):共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩余可用資源;正數表示成功,且有剩余資源。
tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放后允許喚醒后續等待結點返回true,否則返回false。
以ReentrantLock為例,state初始化為0,表示未鎖定狀態。A線程lock()時,會調用tryAcquire()獨占該鎖並將 state+1。此后,其他線程再tryAcquire()時就會失敗,直到A線程unlock()到state=0(即釋放鎖)為止,其它線程才有機會 獲取該鎖。當然,釋放鎖之前,A線程自己是可以重復獲取此鎖的(state會累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多么次,這樣才能 保證state是能回到零態的。
再以CountDownLatch以例,任務分為N個子線程去執行,state也初始化為N(注意N要與線程個數一致)。這N個子線程是並行執行的,每個 子線程執行完后countDown()一次,state會CAS減1。等到所有子線程都執行完后(即state=0),會unpark()主調用線程,然 后主調用線程就會從await()函數返回,繼續后余動作。
一般來說,自定義同步器要么是獨占方法,要么是共享方式,他們也只需實現tryAcquire-tryRelease、 tryAcquireShared-tryReleaseShared中的一種即可。但AQS也支持自定義同步器同時實現獨占和共享兩種方式,如 ReentrantReadWriteLock。
雙向CLH鏈表
AQS核心業務邏輯
1.AQS中用state屬性表示鎖同步狀態,如果能成功將state屬性通過CAS操作從0設置成1即獲取了鎖. 當state>0時表示已經獲取了鎖,當state = 0無鎖。
2.獲取了鎖的線程才能將exclusiveOwnerThread設置成自己
3.addWaiter負責將當前等待鎖的線程包裝成Node,並成功地添加到隊列的末尾,這一點是由它調用的enq方法保證的,enq方法同時還負責在隊列為空時初始化隊列。
4.acquireQueued方法用於在Node成功入隊后,繼續嘗試獲取鎖(取決於Node的前驅節點是不是head),或者將線程掛起
5.shouldParkAfterFailedAcquire方法用於保證當前線程的前驅節點的waitStatus屬性值為SIGNAL,從而保證了自己掛起后,前驅節點會負責在合適的時候喚醒自己。
6.parkAndCheckInterrupt方法用於掛起當前線程,並檢查中斷狀態。
7.如果最終成功獲取了鎖,線程會從lock()方法返回,繼續往下執行;否則,線程會阻塞等待。
AQS三板斧
狀態
volatile state屬性
private volatile int state;
該屬性的值即表示了鎖的狀態,state為0表示鎖沒有被占用,state大於0表示當前已經有線程持有該鎖,這里之所以說大於0而不說等於1是因為可能存在可重入的情況。你可以把state變量當做是當前持有該鎖的線程數量。
CAS 操作用來改變狀態
waitStatus 的狀態值
它不是表征當前節點的狀態,而是當前節點的下一個節點的狀態。
當 一個節點的waitStatus被置為SIGNAL,就說明它的下一個節點(即它的后繼節點)已經被掛起了(或者馬上就要被掛起了),因此在當前節點釋放 了鎖或者放棄獲取鎖時,如果它的waitStatus屬性為SIGNAL,它還要完成一個額外的操作——喚醒它的后繼節點。
表示Node所代表的當前線程已經取消了排隊,即放棄獲取鎖了。
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
CAS操作
AQS的3個屬性state,head和tail
Node對象的兩個屬性waitStatus,next
CAS操作主要針對5個屬性。
1 private static final Unsafe unsafe = Unsafe.getUnsafe(); 2 private static final long stateOffset; 3 private static final long headOffset; 4 private static final long tailOffset; 5 private static final long waitStatusOffset; 6 private static final long nextOffset; 7 8 static { 9 try { 10 stateOffset = unsafe.objectFieldOffset 11 (AbstractQueuedSynchronizer.class.getDeclaredField("state")); 12 headOffset = unsafe.objectFieldOffset 13 (AbstractQueuedSynchronizer.class.getDeclaredField("head")); 14 tailOffset = unsafe.objectFieldOffset 15 (AbstractQueuedSynchronizer.class.getDeclaredField("tail")); 16 waitStatusOffset = unsafe.objectFieldOffset 17 (Node.class.getDeclaredField("waitStatus")); 18 nextOffset = unsafe.objectFieldOffset 19 (Node.class.getDeclaredField("next")); 20 21 } catch (Exception ex) { throw new Error(ex); } 22 }
CAS操作碼
CAS操作是最輕量的並發處理,通常我們對於狀態的修改都會用到CAS操作,因為狀態可能被多個線程同時修改,CAS操作保證了同一個時刻,只有一個線程能修改成功,從而保證了線程安全,CAS操作基本是由Unsafe工具類的compareAndSwapXXX
來實現的;CAS采用的是樂觀鎖的思想,因此常常伴隨着自旋,如果發現當前無法成功地執行CAS,則不斷重試,直到成功為止,自旋的的表現形式通常是一個死循環for(;;);
1 protected final boolean compareAndSetState(int expect, int update) { 2 return unsafe.compareAndSwapInt(this, stateOffset, expect, update); 3 } 4 private final boolean compareAndSetHead(Node update) { 5 return unsafe.compareAndSwapObject(this, headOffset, null, update); 6 } 7 private final boolean compareAndSetTail(Node expect, Node update) { 8 return unsafe.compareAndSwapObject(this, tailOffset, expect, update); 9 } 10 private static final boolean compareAndSetWaitStatus(Node node, int expect,int update) { 11 return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update); 12 } 13 private static final boolean compareAndSetNext(Node node, Node expect, Node update) { 14 return unsafe.compareAndSwapObject(node, nextOffset, expect, update); 15 }
隊列
AQS中,隊列的實現是一個雙向鏈表,被稱為sync queue,它表示所有等待鎖的線程的集合
AQS 中的隊列是一個CLH隊列,它的head節點永遠是一個啞結點(dummy node), 它不代表任何線程(某些情況下可以看做是代表了當前持有鎖的線程),因此head所指向的Node的thread屬性永遠是null。只有從次頭節點往后 的所有節點才代表了所有等待鎖的線程。也就是說,在當前線程沒有搶到鎖被包裝成Node扔到隊列中時,即使隊列是空的,它也會排在第二個,我們會在它的前 面新建一個dummy節點
在並發編程中使用隊列通常是將當前線程包裝成某種類型的數據結構扔到等待隊列中.
隊列中的節點數據結構。
1 static final class Node { 2 3 // 共享 4 static final Node SHARED = new Node(); 5 // 獨占 6 static final Node EXCLUSIVE = null; 7 8 /** 9 * 因為超時或者中斷,節點會被設置為取消狀態,被取消的節點時不會參與到競爭中的,他會一直保持取消狀態不會轉變為其他狀態 10 */ 11 static final int CANCELLED = 1; 12 /** 13 * 后繼節點的線程處於等待狀態,而當前節點的線程如果釋放了同步狀態或者被取消,將會通知后繼節點,使后繼節點的線程得以運行 14 * (說白了就是處於等待被喚醒的線程(或是節點)只要前繼結點釋放鎖,就會通知標識為SIGNAL狀態的后繼結點的線程執行) 15 */ 16 static final int SIGNAL = -1; 17 /** 18 * 節點在等待隊列中,節點線程等待在Condition上,當其他線程對Condition調用了signal()后,該節點將會從等待隊列中轉移到同步隊列中,加入到同步狀態的獲取中 19 */ 20 static final int CONDITION = -2; 21 /** 22 * 表示下一次共享式同步狀態獲取,將會無條件地傳播下去 23 */ 24 static final int PROPAGATE = -3; 25 26 /** 等待狀態 */ 27 volatile int waitStatus; 28 29 /** 前驅節點,當節點添加到同步隊列時被設置(尾部添加) */ 30 volatile Node prev; 31 32 /** 后繼節點 */ 33 volatile Node next; 34 35 /** 等待隊列中的后續節點。如果當前節點是共享的,那么字段將是一個 SHARED 常量,也就是說節點類型(獨占和共享)和等待隊列中的后續節點共用同一個字段 */ 36 Node nextWaiter; 37 38 /** 獲取同步狀態的線程 */ 39 volatile Thread thread; 40 41 final boolean isShared() { 42 return nextWaiter == SHARED; 43 } 44 45 final Node predecessor() throws NullPointerException { 46 Node p = prev; 47 if (p == null) 48 throw new NullPointerException(); 49 else 50 return p; 51 } 52 53 Node() { // Used to establish initial head or SHARED marker 54 } 55 56 Node(Thread thread, Node mode) { // Used by addWaiter 57 this.nextWaiter = mode; 58 this.thread = thread; 59 } 60 61 Node(Thread thread, int waitStatus) { // Used by Condition 62 this.waitStatus = waitStatus; 63 this.thread = thread; 64 } 65 66 }
狀態變量waitStatus
表示當前Node所代表的線程的等待鎖的狀態,在獨占鎖模式下,我們只需要關注CANCELLED SIGNAL兩種狀態即可。
nextWaiter屬性
在獨占鎖模式下永遠為null,僅僅起到一個標記作用,沒有實際意義。
AQS2種隊列
同步等待隊列
AQS當中的同步等待隊列也稱CLH隊列,CLH隊列是Craig、Landin、Hagersten三人發明的一種基於雙向鏈表數據結構的隊列,是FIFO先入先出線程等待隊列,Java中的CLH隊列是原CLH隊列的一個變種,線程由原自旋機制改為阻塞機制。
不過這里有一點我們提前說一下,在AQS中的隊列是一個CLH隊列,它的head節點永遠是一個啞結點(dummy node), 它不代表任何線程(某些情況下可以看做是代表了當前持有鎖的線程),因此head所指向的Node的thread屬性永遠是null。只有從次頭節點往后的所有節點才代表了所有等待鎖的線程。也就是說,在當前線程沒有搶到鎖被包裝成Node扔到隊列中時,即使隊列是空的,它也會排在第二個,我們會在它的前面新建一個dummy節點(具體的代碼我們在后面分析源碼時再詳細講)。為了便於描述,下文中我們把除去head節點的隊列稱作是等待隊列,在這個隊列中的節點才代表了所有等待鎖的線程。
thread
:表示當前Node所代表的線程
waitStatus
:表示節點所處的等待狀態,共享鎖模式下只需關注三種狀態:SIGNAL
CANCELLED
初始態(0)
prev
next
:節點的前驅和后繼
nextWaiter
:進作為標記,值永遠為null,表示當前處於獨占鎖模式
條件等待隊列
Condition是一個多線程間協調通信的工具類,使得某個,或者某些線程一起等待某個條件(Condition),只有當該條件具備時,這些等待線程才會被喚醒,從而重新爭奪鎖。
AQS核心屬性
鎖相關的屬性有兩個
private volatile int state; //鎖的狀態
private transient Thread exclusiveOwnerThread; // 當前持有鎖的線程,注意這個屬性是從AbstractOwnableSynchronizer繼承而來
sync queue相關的屬性有兩個
private transient volatile Node head; // 隊頭,為dummy node
private transient volatile Node tail; // 隊尾,新入隊的節點
隊列中的Node屬性
1 // 節點所代表的線程 2 volatile Thread thread; 3 4 // 雙向鏈表,每個節點需要保存自己的前驅節點和后繼節點的引用 5 volatile Node prev; 6 volatile Node next; 7 8 // 線程所處的等待鎖的狀態,初始化時,該值為0 9 volatile int waitStatus; 10 static final int CANCELLED = 1; 11 static final int SIGNAL = -1;
acquire分析
tryAcquire()嘗試直接去獲取資源,如果成功則直接返回;
addWaiter()將該線程加入等待隊列的尾部,並標記為獨占模式;
acquireQueued()使線程在等待隊列中獲取資源,一直獲取到資源后才返回。如果在整個等待過程中被中斷過,則返回true,否則返回false。
如果線程在等待過程中被中斷過,先不響應的。在獲取資源后才再進行自我中斷selfInterrupt()。
tryAcquire(arg) : 獲取鎖的業務邏輯
判斷當前鎖有沒有被占用:
1.如果鎖沒有被占用, 嘗試以公平的方式獲取鎖
2.如果鎖已經被占用, 檢查是不是鎖重入
獲取鎖成功返回true, 失敗則返回false
addWaiter(Node mode)
當tryAcquire失敗后,才會調用acquireQueued(addWaiter(Node.EXCLUSIVE), arg),addWaiter方法用於將當前線程加入到等待隊列的隊尾,並返回當前線程所在的結點。
使用了自旋保證插入隊尾成功。
在獲取鎖失敗后調用, 將當前請求鎖的線程包裝成Node扔到sync queue中去,並返回這個Node。
1 private Node addWaiter(Node mode) { 2 Node node = new Node(Thread.currentThread(), mode); 3 // Try the fast path of enq; backup to full enq on failure 4 Node pred = tail; 5 // 如果隊列不為空, 則用CAS方式將當前節點設為尾節點 6 if (pred != null) { 7 node.prev = pred; 8 // 檢查tail的狀態,如果當前是pred 9 if (compareAndSetTail(pred, node)) { // 將當前節點設為尾節點 10 pred.next = node; // 把tail的next節點指向當前Node 11 return node; 12 } 13 } 14 15 16 // 代碼會執行到這里, 只有兩種情況: 17 // 1. 隊列為空 18 // 2. CAS失敗 19 // 注意, 這里是並發條件下, 所以什么都有可能發生, 尤其注意CAS失敗后也會來到這里. 例如: 有可能其他線程已經成為了新的尾節點,導致尾節點不再是我們之前看到的那個pred了。 20 21 // 如果當前node插入隊尾失敗,則通過自旋保證替換成功(自旋+CAS) 22 enq(node); 23 return node; 24 }
enq()方法:在該方法中, 我們使用了死循環, 即以自旋方式將節點插入隊列,如果失敗則不停的嘗試, 直到成功為止, 另外, 該方法也負責在隊列為空時, 初始化隊列,這也說明,隊列是延時初始化的(lazily initialized):
1 private Node enq(final Node node) { 2 for (;;) { 3 Node t = tail; 4 // 如果是空隊列, 首先進行初始化 5 // 這里也可以看出, 隊列不是在構造的時候初始化的, 而是延遲到需要用的時候再初始化, 以提升性能 6 if (t == null) { 7 // 注意,初始化時使用new Node()方法新建了一個dummy節點 8 // 從這里可以看出, 在這個等待隊列中,頭結點是一個“啞節點”,它不代表任何等待的線程。 9 // head節點不代表任何線程,它就是一個空節點! 10 if (compareAndSetHead(new Node())) 11 tail = head; // 這里僅僅是將尾節點指向dummy節點,並沒有返回 12 } else { 13 // 到這里說明隊列已經不是空的了, 這個時候再繼續嘗試將節點加到隊尾 14 15 // 1.設置node的前驅節點為當前的尾節點 16 node.prev = t; 17 18 // 2.修改tail屬性,使它指向當前節點; 這里的CAS保證了同一時刻只有一個節點能成為尾節點,其他節點將失敗,失敗后將回到for循環中繼續重試。 19 if (compareAndSetTail(t, node)) { 20 21 // 3.修改原來的尾節點,使它的next指向當前節點 22 t.next = node; 23 return t; 24 } 25 } 26 } 27 }
添加到queue隊尾步驟
將一個節點node添加到sync queue的末尾需要三步:
設置node的前驅節點為當前的尾節點:node.prev = t
修改tail屬性,使它指向當前節點
修改原來的尾節點,使它的next指向當前節點尾分叉。
需要注意,這里的三步並不是一個原子操作,第一步很容易成功;而第二步由於是一個CAS操作,在並發條件下有可能失敗,第三步只有在第二步成功的條件下才執行。這里的CAS保證了同一時刻只有一個節點能成為尾節點,其他節點將失敗,失敗后將回到for循環中繼續重試所以,當有大量的線程在同時入隊的時候,同一時刻,只有一個線程能完整地完成這三步,而其他線程只能完成第一步,於是就出現了尾分叉:
這 里第三步是在第二步執行成功后才執行的,這就意味着,有可能即使我們已經完成了第二步,將新的節點設置成了尾節點,此時原來舊的尾節點的next值可能還 是null(因為還沒有來的及執行第三步),所以如果此時有線程恰巧從頭節點開始向后遍歷整個鏈表,則它是遍歷不到新加進來的尾節點的,但是這顯然是不合 理的,因為現在的tail已經指向了新的尾節點。
另一方面,當我們完成了第二步之后,第一步一定是完成了的,所以如果我們從尾節點開始向前遍歷,已經可以遍歷到所有的節點。
這也就是為什么我們在AQS相關的源碼中 (比如:unparkSuccessor(Node node) 中的:
1 for (Node t = tail; t != null && t != node; t = t.prev))
通常是從尾節點開始逆向遍歷鏈表——因為一個節點要能入隊,則它的prev屬性一定是有值的,但是它的next屬性可能暫時還沒有值。
至於那些“分叉”的入隊失敗的其他節點,在下一輪的循環中,它們的prev屬性會重新指向新的尾節點,繼續嘗試新的CAS操作,最終,所有節點都會通過自旋不斷的嘗試入隊,直到成功為止。
acquireQueued(final Node node, int arg)
addWaiter的將當前線程加入隊列后,使用acquireQueued進行阻塞,直到獲取到資源后返回
1 final boolean acquireQueued(final Node node, int arg) { 2 boolean failed = true; 3 try { 4 boolean interrupted = false; 5 for (;;) { 6 final Node p = node.predecessor(); 7 // 當前節點的前驅是 head 節點時, 再次嘗試獲取鎖 8 if (p == head && tryAcquire(arg)) { 9 setHead(node); 10 p.next = null; // help GC 11 failed = false; 12 return interrupted; 13 } 14 //在獲取鎖失敗后, 判斷是否需要把當前線程掛起 15 if (shouldParkAfterFailedAcquire(p, node) && 16 parkAndCheckInterrupt()) 17 interrupted = true; 18 } 19 } finally { 20 if (failed) 21 cancelAcquire(node); 22 } 23 }
shouldParkAfterFailedAcquire(Node pred, Node node)
這個函數只有在當前節點的前驅節點的waitStatus狀態本身就是SIGNAL的時候才會返回true, 其他時候都會返回false:
1 // Returns true if thread should block. 2 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 3 int ws = pred.waitStatus; // 獲得前驅節點的ws 4 if (ws == Node.SIGNAL) 5 // 前驅節點的狀態已經是SIGNAL了(This node has already set status asking a release),說明鬧鍾已經設了,可以直接高枕無憂地睡了(so it can safely park) 6 return true; 7 if (ws > 0) { 8 // 當前節點的 ws > 0, 則為 Node.CANCELLED 說明前驅節點已經取消了等待鎖(由於超時或者中斷等原因) 9 // 既然前驅節點不等了, 那就繼續往前找, 直到找到一個還在等待鎖的節點 10 // 然后我們跨過這些不等待鎖的節點, 直接排在等待鎖的節點的后面 (是不是很開心!!!) 11 do { 12 node.prev = pred = pred.prev; 13 } while (pred.waitStatus > 0); 14 pred.next = node; 15 } else { 16 // 前驅節點的狀態既不是SIGNAL,也不是CANCELLED 17 // 用CAS設置前驅節點的ws為 Node.SIGNAL,給自己定一個鬧鍾 18 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 19 } 20 return false; 21 }
parkAndCheckInterrupt()
到這個函數已經是最后一步了, 就是將線程掛起, 等待被喚醒. Convenience method to park and then check if interrupted. return true if interrupted:
1 private final boolean parkAndCheckInterrupt() { 2 LockSupport.park(this); // 線程被掛起,停在這里不再往下執行了 3 return Thread.interrupted(); 4 }
LockSupport.park()
public class LockSupport extends Object用於創建鎖和其他同步類的基本線程阻塞原語:
1 public static void park(Object blocker) { 2 Thread t = Thread.currentThread(); 3 setBlocker(t, blocker); 4 UNSAFE.park(false, 0L); 5 setBlocker(t, null); 6 } 7 8 private static void setBlocker(Thread t, Object arg) { 9 // Even though volatile, hotspot doesn't need a write barrier here. 10 UNSAFE.putObject(t, parkBlockerOffset, arg); 11 }
總結
感謝網絡大神的分享,
https://juejin.im/post/5aeb07ab6fb9a07ac36350c8
https://www.cnblogs.com/waterystone/p/4920797.html
https://mp.weixin.qq.com/s?__biz=MzA5OTI2MTE3NA==&mid=2658337633&idx=1&sn=6a18fc2310406a2f35ccd4bb7db41a54&chksm=8b02acf8bc7525ee5714d223efd4c27b41ae7d938518f8de8e52faa8a68601167699aac73b9f&mpshare=1&scene=1&srcid=0105vqyUJ4LKmg9TDVyoQdDk&sharer_sharetime=1578208728620&sharer_shareid=d40e8d2bb00008844e69867bcfc0d895#rd
https://www.zfl9.com/java-juc-framework.html