JAVA篇:Java 多執行緒 (二) 執行緒鎖機制和死鎖
2、執行緒鎖機制和死鎖
關鍵字:Java鎖分類名詞、執行緒死鎖、Lock、ReentrantLock、ReadWriteLock、Condition
說到鎖的話,總是會提到很多,其分類與理論部分應該會參考別人的描述,反正自己講也不會比別人好。
-
公平鎖/非公平鎖
-
可重入鎖
-
獨享鎖/共享鎖
-
互斥鎖/讀寫鎖
-
樂觀鎖/悲觀鎖
-
分段鎖
-
偏向鎖/輕量級鎖/重量級鎖
-
自旋鎖
還有一部分則是Java中鎖的實現與應用。
-
synchronized
-
Lock相關類
-
Condition相關類
2.1 鎖的分類名詞
前面所說的鎖的分類名詞,有的是指鎖的狀態、有的指鎖的特性、有的指鎖的設計。這部分主要是參考
2.1.1 公平鎖/非公平鎖
公平鎖是指多個執行緒按照申請所的順序來獲取鎖。
非公平鎖是指多個執行緒獲取鎖的順序並不是按照申請鎖的順序,有可能是後申請的執行緒比先申請的執行緒有限獲取鎖。有可能,會造成優先順序反轉或者飢餓現象。
非公平鎖的優點在於吞吐量比公平鎖大。
在Java中,synchronized
是一種非公平鎖。
ReentrantLock
則可以通過建構函式指定該鎖是否公平鎖,預設是非公平鎖。ReentrantLock
通過AQS來實現執行緒排程,實現公平鎖。
2.1.2 可重入鎖
可重入鎖又名遞迴鎖,是指在同一個執行緒在持有鎖的前提下,再遇到需要申請同一個鎖的情況時可自動獲取鎖。而非可重入鎖遇到這種情況會形成死鎖,也就是“我申請我已經持有的鎖,我不會釋放鎖也申請不到鎖,所以形成死鎖。”
Java中,
synchronized
在JDK 1.6優化後,屬於可重入鎖。
ReentrantLock
,即Re entrant Lock
,可重入鎖。
synchronized void A(){ System.out.println("A獲取鎖!"); B(); } synchronized void B(){ System.out.println("B鎖重入成功!"); }
2.1.3 獨享鎖/共享鎖
獨享鎖是指該鎖一次只能被一個執行緒所持有,共享鎖是指該鎖可被多個執行緒所持有。
在Java中,
synchronized
屬於獨享鎖。
ReentrantLock
而Lock的另一個實現類ReadWriteLock
,其讀鎖是共享鎖,其寫鎖是獨享鎖。讀鎖的共享鎖可保證高效的併發讀,但是讀寫、寫讀、寫寫的過程是互斥的,防止髒讀、資料丟失。獨享鎖和共享鎖也是通過AQS實現的。
2.1.4 互斥鎖/讀寫鎖
上面講的獨享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實現。
互斥鎖在Java中的具體實現就是ReentraLock
讀寫鎖在Java中的具體實現就是ReadWriteLock
2.1.5 樂觀鎖/悲觀鎖
樂觀鎖與悲觀鎖不是指具體的什麼型別的鎖,而是指看待併發同步的角度。
悲觀鎖認為對同一個資料的併發操作,一定是會發生修改的,哪怕沒有修改,也會認為修改。因此對於同一個資料的併發操作,悲觀鎖採取加鎖的形式,悲觀地認為,不加鎖的併發操作一定會出現問題。
樂觀鎖則認為對於同一個資料的併發操作,是不會發生修改的。在更新資料的時候,會採用嘗試更新,不斷更新的方式更新資料,樂觀地認為,不加鎖的併發操作是沒有事情的。
從上面的描述我們可以看出,悲觀鎖適合寫操作非常多的場景,樂觀鎖適合讀操作非常多的場景,不加鎖會帶來大量的效能提升。
悲觀鎖在Java中的使用就是利用各種鎖。
樂觀鎖在Java中的使用就是無鎖程式設計,常常採用的是CAS演算法,典型的例子就是原子類,通過CAS自旋實現原子操作的更新。
2.1.6 分段鎖
分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap
而言,其併發的實現就是通過分段鎖的形式來實現高效的併發操作。
我們以ConcurrentHashMap
來說一下分段鎖的含義以及設計思想,ConcurrentHashMap
中的分段鎖稱為segment,它類似於HashMap(JDK7與JDK8中HashMap的實現)的結構,即內部擁有一個Entry陣列,陣列中的每個元素又是一個連結串列;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。
當需要put元素的時候,並不是對整個hashmap進行加鎖,而是先通過hashcode來知道它要放在哪一個分段中,然後對這個分段進行加鎖,所以當多執行緒put的時候,只要不死放在一個酚酸中,就實現了真正的並行插入。
但是在統計size的時候,即獲取hashmap全域性資訊的時候,就需要獲取所有的分段鎖才能統計。
分段鎖的設計目的就是細化鎖的粒度,當操作不需要更新整個陣列的時候,就針對資料的一項進行加鎖操作。
2.1.7 偏向鎖/輕量級鎖/重量級鎖
這三種所是指鎖的狀態,並且是針對synchronized
。在 java 6通過引入鎖的升級機制來實現高效synchronized
。這三種鎖的狀態是通過物件監視器在物件頭中的欄位來表明的。
偏向鎖是指一段同步程式碼一直被一個執行緒所訪問,那麼該執行緒會自動獲取鎖,降低獲取鎖的代價。
輕量級鎖是指當鎖是偏向鎖的時候,被另一個執行緒所訪問,偏向鎖就會升級為輕量級鎖,其他執行緒會通過自旋的形式嘗試獲取鎖,不會阻塞,提高效能。
重量級鎖是指當鎖為輕量級鎖的時候,另一個執行緒雖然是自旋,但自旋不會持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓其他申請的執行緒進入阻塞,效能降低。
2.1.8 自旋鎖
在Java中。自旋鎖是指嘗試獲取鎖的執行緒不會立即阻塞,而是採用迴圈的方式去嘗試獲取鎖,這樣的好處是減少執行緒上下文切換的消耗,缺點是迴圈會消耗CPU。
2.2 執行緒死鎖
死鎖是一個經典的多執行緒問題。避免死鎖重要嗎?一旦一組Java執行緒發生死鎖,那麼這組執行緒及鎖涉及其已經持有鎖的資源區將不再可用--除非重啟應用。
死鎖是設計上的bug,它並不一定發生,但它有可能發生,而且發生的情況一般出現在極端的高負載的情況下。
那麼有什麼辦法為了避免死鎖?
-
讓程式每次至多隻能獲得一個鎖。但這個在多執行緒環境下通常不現實。
-
設計時考慮清楚鎖的順序,儘量減少潛在的加鎖互動數量
-
避免使用synchronized,為執行緒等待設定等待上限,避免無限等待。
2.3 Lock和Condition
JVM提供了synchronized關鍵字來實現對變數的同步訪問以及用wait和notify來實現執行緒間通訊。在jdk1.5以後,Java提供了Lock類來提供更加豐富的鎖功能,並且還提供了Condition來實現執行緒間通訊。
Lock和Condition都屬java.util.concurrent.locks
下的介面,如下圖所示。其中Lock的實現類包含ReentrantLock
和ReadWriteLock
,而Condition物件是通過lock物件建立的。
其中的AbstractOwnableSynchronizer(AQS),即抽象的佇列式的同步器,定義了一套多執行緒訪問共享資源的同步器框架,許多同步類實現都依賴於它,如ReentrantLock/Semaphore/CountDownLatch...
在這裡對AQS暫不做展開。
2.4 ReentrantLock
之前有說過ReentrantLock是一個可重入互斥的鎖,在其建構函式中可以設定其是否是公平鎖。
/* 建立一個 ReentrantLock的例項。*/ ReentrantLock() /* 根據給定的公平政策建立一個 ReentrantLock的例項。。*/ ReentrantLock(boolean fair)
相比於synchronized,ReentrantLock提供了更加豐富靈活的功能。
-
void lock():獲得鎖。
-
void unlock():嘗試釋放此鎖。
-
boolean tryLock():只有在呼叫時它不被另一個執行緒佔用才能獲取鎖。
-
boolean tryLock(long timeout, TimeUnit unit):有限制超時的請求鎖,如果執行緒沒有被中斷且沒有超時則可以獲得鎖。
-
void lockInterruptibly() : 獲取鎖,除非被中斷。
-
Condition newCondition():返回一個Condition例項。
-
boolean isFair():該鎖是否為公平鎖。
-
boolean isHeldByCurrentThread():查詢此鎖是否由當前執行緒持有。
-
boolean isLocked():查詢此鎖是否由任何執行緒持有。
-
int getHoldCount():查詢當前執行緒對此鎖的阻塞數量。
-
protected Thread getOwner():返回當前擁有此鎖的執行緒,如果不擁有,則返回 null 。
-
protected Collection<Thread> getQueuedThreads():返回可能正在等待獲取此鎖的執行緒的集合。
-
int getQueueLength():返回等待獲取此鎖的執行緒數的估計。
-
boolean hasQueuedThread(Thread thread):查詢給定執行緒是否等待獲取此鎖。
-
boolean hasQueuedThreads():查詢是否有執行緒正在等待獲取此鎖。
-
protected Collection<Thread> getWaitingThreads(Condition condition):返回正在等待(wait)給定Condition的執行緒集合,傳入的Condition必須與鎖相關聯。
-
int getWaitQueueLength(Condition condition):返回正在等待(wait)給定Condition的執行緒數量估計,傳入的Condition必須與鎖相關聯。
-
boolean hasWaiters(Condition condition):查詢是否有任何執行緒等待(wait)給定Condition,傳入的Condition必須與鎖相關聯。
2.4.1 請求鎖釋放鎖
ReentrantLock除了提供與synchronized鎖功能相似的無限阻塞的鎖請求lock()/unlock(),還提供了可限制阻塞超時的tryLock()/tryLock(timeout),可以更加靈活地處理鎖相關的操作,防止執行緒死鎖。
同時還可以檢視當前鎖持有、阻塞的情況。
測試程式碼如下:
/* 測試ReentrantLock鎖請求與釋放 */ public void test1(){ /* 建立鎖例項ReentrantLock */ Lock rlock = new ReentrantLock(); // System.out.println(rlock.toString()); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); /* 執行緒1從頭到尾持有鎖 */ Runnable r1 = new Runnable() { @Override public void run() { rlock.lock(); System.out.println(df.format(new Date())+" part11:子執行緒1先持有鎖rlock,然後休眠5S"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(df.format(new Date())+" part12:子執行緒1釋放鎖rlock"); rlock.unlock(); } }; Thread t1 = new Thread(r1); t1.start(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } /* 執行緒2使用lock申請鎖 */ Runnable r2 = new Runnable() { @Override public void run() { System.out.println(df.format(new Date())+" part21:子執行緒2使用lock()申請鎖rlock,會阻塞"); rlock.lock(); System.out.println(df.format(new Date())+" part22:子執行緒2獲得了鎖rlock,然後釋放鎖"); rlock.unlock(); } }; Thread t2 = new Thread(r2); t2.start(); /* 執行緒3使用trylock\trylock(timeout)申請鎖 */ Runnable r3 = new Runnable() { @Override public void run() { System.out.println(df.format(new Date())+" part31:子執行緒3使用trylock()申請鎖rlock"); if(rlock.tryLock()){ System.out.println(df.format(new Date())+" part32:子執行緒3獲得了鎖rlock,然後釋放鎖"); rlock.unlock(); }else{ System.out.println(df.format(new Date())+" part33:子執行緒3沒有獲得鎖rlock,使用帶1S超時的trylock申請鎖"); try { if(rlock.tryLock(1000, TimeUnit.MILLISECONDS)){ System.out.println(df.format(new Date())+" part34:子執行緒3獲得了鎖rlock,然後釋放鎖"); rlock.unlock(); }else{ System.out.println(df.format(new Date())+" part35:子執行緒3沒有獲得鎖rlock,超時退出"); } } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread t3 = new Thread(r3); t3.start(); /* 主執行緒檢視當前鎖與執行緒的情況 */ System.out.println(df.format(new Date())+" 主執行緒檢視-rlock是否為公平鎖:"+((ReentrantLock) rlock).isFair()); System.out.println(df.format(new Date())+" 主執行緒檢視-rlock是否被任意執行緒持有:"+((ReentrantLock) rlock).isLocked()); System.out.println(df.format(new Date())+" 主執行緒檢視-查詢是否有執行緒等待獲取rlock:"+((ReentrantLock) rlock).hasQueuedThreads()); System.out.println(df.format(new Date())+" 主執行緒檢視-查詢子執行緒2是否等待獲取鎖rlock:"+((ReentrantLock) rlock).hasQueuedThread(t2)); System.out.println(df.format(new Date())+" 主執行緒檢視-查詢等待獲取鎖rlock的執行緒數估計:"+((ReentrantLock) rlock).getQueueLength()); /* 主執行緒嘗試獲取鎖 */ rlock.lock(); System.out.println(df.format(new Date())+" part01:主執行緒獲得鎖rlock"); System.out.println(df.format(new Date())+" 主執行緒檢視-rlock是否被主執行緒持有:"+((ReentrantLock) rlock).isHeldByCurrentThread()); System.out.println(df.format(new Date())+" 主執行緒檢視-查詢當前執行緒對rlock的阻塞數量:"+((ReentrantLock) rlock).getHoldCount()); rlock.lock(); System.out.println(df.format(new Date())+" part02:主執行緒重入鎖rlock"); System.out.println(df.format(new Date())+" 主執行緒檢視-rlock是否被主執行緒持有:"+((ReentrantLock) rlock).isHeldByCurrentThread()); System.out.println(df.format(new Date())+" 主執行緒檢視-查詢當前執行緒對rlock的阻塞數量:"+((ReentrantLock) rlock).getHoldCount()); rlock.unlock(); rlock.unlock(); System.out.println(df.format(new Date())+"測試結束。"); }
輸出結果如下:
2021-10-11 09:54:03:122 part11:子執行緒1先持有鎖rlock,然後休眠5S 2021-10-11 09:54:03:124 part21:子執行緒2使用lock()申請鎖rlock,會阻塞 2021-10-11 09:54:03:124 主執行緒檢視-rlock是否為公平鎖:false 2021-10-11 09:54:03:125 主執行緒檢視-rlock是否被任意執行緒持有:true 2021-10-11 09:54:03:125 part31:子執行緒3使用trylock()申請鎖rlock 2021-10-11 09:54:03:125 主執行緒檢視-查詢是否有執行緒等待獲取rlock:true 2021-10-11 09:54:03:125 part33:子執行緒3沒有獲得鎖rlock,使用帶1S超時的trylock申請鎖 2021-10-11 09:54:03:125 主執行緒檢視-查詢子執行緒2是否等待獲取鎖rlock:true 2021-10-11 09:54:03:125 主執行緒檢視-查詢等待獲取鎖rlock的執行緒數估計:1 #這裡為什麼會是1? 2021-10-11 09:54:04:132 part35:子執行緒3沒有獲得鎖rlock,超時退出 2021-10-11 09:54:08:123 part12:子執行緒1釋放鎖rlock 2021-10-11 09:54:08:123 part22:子執行緒2獲得了鎖rlock,然後釋放鎖 2021-10-11 09:54:08:124 part01:主執行緒獲得鎖rlock 2021-10-11 09:54:08:124 主執行緒檢視-rlock是否被主執行緒持有:true 2021-10-11 09:54:08:124 主執行緒檢視-查詢當前執行緒對rlock的阻塞數量:1 2021-10-11 09:54:08:125 part02:主執行緒重入鎖rlock 2021-10-11 09:54:08:125 主執行緒檢視-rlock是否被主執行緒持有:true 2021-10-11 09:54:08:125 主執行緒檢視-查詢當前執行緒對rlock的阻塞數量:2 2021-10-11 09:54:08:125測試結束。
對結果有疑惑的地方在於getQueueLength()為什麼會線上程2、3都等待著鎖的情況下,結果是1?後面經驗證,getQueueLength()僅統計lock()後阻塞的執行緒,trylock(timeout)的等待應該底層有所不同。
2.4.2 中斷與鎖請求
lockInterruptibly()與lock()都是鎖請求方法,不過lockInterruptibly()提供了響應中斷請求以及處理中斷請求的功能,使得在進行鎖請求時,執行緒可以被中斷。
測試程式碼如下所示:
/* 測試中斷與鎖請求 */ public void test3() { /* 建立鎖例項ReentrantLock */ Lock rlock = new ReentrantLock(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); /* 子執行緒1使用lock等待 */ Runnable r1 = new Runnable() { @Override public void run() { try { Thread.sleep(100); System.out.println(df.format(new Date()) + " part11:子執行緒1使用lock請求鎖"); rlock.lock(); System.out.println(df.format(new Date()) + " part12:子執行緒1獲得鎖"); rlock.unlock(); System.out.println(df.format(new Date()) + " part13:子執行緒1釋放鎖"); } catch (InterruptedException e) { System.out.println(df.format(new Date()) + " part19:子執行緒1被中斷!"); } } }; Thread t1 = new Thread(r1); t1.start(); /* 子執行緒2使用lockInterruptibly() */ Runnable r2 = new Runnable() { @Override public void run() { try { Thread.sleep(100); System.out.println(df.format(new Date()) + " part21:子執行緒2使用lockInterruptibly請求鎖"); rlock.lockInterruptibly(); System.out.println(df.format(new Date()) + " part22:子執行緒2獲得鎖"); rlock.unlock(); System.out.println(df.format(new Date()) + " part23:子執行緒2釋放鎖"); } catch (InterruptedException e) { System.out.println(df.format(new Date()) + " part29:子執行緒2被中斷!"); } } }; Thread t2 = new Thread(r2); t2.start(); /* 主執行緒先持有鎖使得其他子執行緒等待,然後在合適的時間中斷兩個執行緒 */ System.out.println(" 主執行緒先持有鎖使得其他子執行緒等待,然後在合適的時間中斷兩個執行緒 "); rlock.lock(); System.out.println(df.format(new Date()) + " part01:主執行緒獲得鎖後休眠,防止在sleep的時候中斷"); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(df.format(new Date()) + " part02:主執行緒中斷子執行緒1"); t1.interrupt(); System.out.println(df.format(new Date()) + " part03:主執行緒中斷子執行緒2"); t2.interrupt(); System.out.println(df.format(new Date()) + " part04:主執行緒釋放鎖"); rlock.unlock(); System.out.println(df.format(new Date()) + " 測試結束"); }
結果如下,可以看出來子執行緒2成功被中斷了,而子執行緒1中斷之後並無反應:
主執行緒先持有鎖使得其他子執行緒等待,然後在合適的時間中斷兩個執行緒 2021-10-11 10:22:12:435 part01:主執行緒獲得鎖後休眠,防止在sleep的時候中斷 2021-10-11 10:22:12:539 part21:子執行緒2使用lockInterruptibly請求鎖 2021-10-11 10:22:12:539 part11:子執行緒1使用lock請求鎖 2021-10-11 10:22:12:647 part02:主執行緒中斷子執行緒1 2021-10-11 10:22:12:647 part03:主執行緒中斷子執行緒2 2021-10-11 10:22:12:647 part04:主執行緒釋放鎖 2021-10-11 10:22:12:647 part29:子執行緒2被中斷! 2021-10-11 10:22:12:647 測試結束 2021-10-11 10:22:12:647 part12:子執行緒1獲得鎖 2021-10-11 10:22:12:647 part13:子執行緒1釋放鎖
2.5 ReadWriteLock
鎖會導致執行緒阻塞,大大降低了多執行緒處理資料的效率。前面有提到過對於鎖的優化,可以將讀寫分離,當只需要進行併發讀的時候,並不需要進行加鎖操作。
ReadWriteLock是一個介面,它提供了一個讀鎖和一個寫鎖,其實現在 ReentrantReadWriteLock及其內部靜態類 ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.ReentrantReadWriteLock.WriteLock。
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading. */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing. */ Lock writeLock(); }
2.5.1 ReentrantReadWriteLock
ReentrantReadWriteLock.ReadLock
和ReentrantReadWriteLock.ReentrantReadWriteLock.WriteLock
都提供了基本的鎖操作,lock()、trylock()、tryLock(long timeout, TimeUnit unit)、lockInterruptibly()、unlock()、newCondition()。其中WriteLock
還包含方法isHeldByCurrentThread()和getHoldCount()。
讀鎖和寫鎖的例項物件分別由ReentrantReadWriteLock的兩個例項方法readLock()和writeLock()返回。兩個鎖成對,在申請鎖的時候有一定的關聯。
讀鎖ReadLock
申請鎖、獲取鎖的前提是,同一ReentrantReadWriteLock
的寫鎖不被其他執行緒佔有。
寫鎖WriteLock
申請鎖、獲取鎖的前提是,寫鎖及同一ReentrantReadWriteLock
的讀鎖不被其他執行緒佔有。
2.5.2 讀寫鎖測試程式碼
讀寫鎖主要是為了防止多執行緒操作檔案是發生衝突,導致檔案結果與預期不符,同時讀鎖又保證了併發讀取資料時多執行緒效率問題。這裡測試程式碼主要看鎖的情況,有關於多執行緒讀寫後面再拓展討論。
測試程式碼如下,執行過程中可以看到偶爾會有多個執行緒同時進行讀操作:
/* 測試讀寫鎖 */ public void test4(){ /* 建立讀寫鎖 */ ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock readlock = lock.readLock(); ReentrantReadWriteLock.WriteLock writelock = lock.writeLock(); Random random = new Random(); class RThread extends Thread{ private int tag; public RThread(int tag, ReentrantReadWriteLock lock){ this.tag = tag; } @Override public void run(){ int n = 5; while (n>0){ n--; /*隨機休眠 try { Thread.sleep(random.nextInt(300)); } catch (InterruptedException e) { e.printStackTrace(); }*/ readlock.lock(); System.out.println(String.format("讀取執行緒%d:當前讀鎖數量:%d,是否寫鎖定:%b",this.tag,lock.getReadLockCount(),lock.isWriteLocked())); System.out.println(String.format("讀取執行緒%d:進行讀取......剩餘次數:%d",this.tag,n)); readlock.unlock(); } } } class WThread extends Thread{ private int tag; public WThread(int tag,ReentrantReadWriteLock lock){ this.tag = tag; } @Override public void run(){ int n = 5; while (n>0){ n--; writelock.lock(); System.out.println(String.format("寫入執行緒%d:當前讀鎖數量:%d,是否寫鎖定:%b",this.tag,lock.getReadLockCount(),lock.isWriteLocked())); System.out.println(String.format("寫入執行緒%d:進行寫入......剩餘次數:%d",this.tag,n)); writelock.unlock(); } } } /*建立5個讀執行緒,5個寫執行緒*/ int tlength = 5; Thread[] rthreads = new RThread[5]; Thread[] wthreads = new WThread[5]; for(int i=0;i<tlength;i++){ rthreads[i] = new RThread(i,lock); rthreads[i].start(); wthreads[i] = new WThread(i,lock); wthreads[i].start(); } }
2.6 Condition
Condition將物件監視器方法(wait、notify和notifyAll)分解為不同的物件,Lock取代了synchronized,而Condition取代了物件監視器方法的使用。
Condition與Lock例項繫結,通過newCondition()方法建立。包含如下方法:
-
void await():呼叫後當前執行緒進入等待,直到被喚醒或者被中斷。
-
void awaitUninterruptibly():使當前執行緒等待直到被喚醒。該方法不會被中斷。
-
boolean await(long time, TimeUnit unit):使當前執行緒等待直到被喚醒或被中斷,或指定的等待時間過去,返回是否超時的判定。
-
long awaitNanos(long nanosTimeout):使當前執行緒等待直到被喚醒或中斷,或指定的等待時間過去,返回剩餘等待時間。
-
boolean awaitUntil(Date deadline):使當前執行緒等待直到發出訊號或中斷,或者指定的最後期限過去。
-
void signal():喚醒一個等待執行緒。
-
void signalAll():喚醒所有等待執行緒。
需要注意的是:除了呼叫Condition的等待及喚醒方法,包括lock的方法lock.hasWaiters(condition)和lock.getWaitQueueLength(condition),使用condition相關方法前必須已經獲得對應的lock,否則會報錯“java.lang.IllegalMonitorStateException”。
測試程式碼如下:
/* 測試Condition */ public void test5(){ /* 建立鎖和condition */ ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); /* 建立等待子執行緒 */ Runnable r1 = new Runnable() { @Override public void run() { lock.lock(); System.out.println(Thread.currentThread().getName()+": 獲得鎖並進入等待。"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+": 被喚醒並獲得鎖,執行結束退出鎖。"); lock.unlock(); } }; int tlength = 5; Thread[] threads = new Thread[tlength]; for(int i=0;i<tlength;i++){ threads[i] = new Thread(r1); threads[i].start(); } /* 主執行緒進行喚醒執行緒 */ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } lock.lock(); System.out.println(String.format("主執行緒:等待鎖lock的執行緒數估計:%d, 是否有執行緒正在等待condition:%b,估計等待執行緒數:%d", lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition))); System.out.println("主執行緒:signal()"); condition.signal(); System.out.println(String.format("主執行緒:等待鎖lock的執行緒數估計:%d, 是否有執行緒正在等待condition:%b,估計等待執行緒數:%d", lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition))); System.out.println("主執行緒:signal()"); condition.signal(); System.out.println(String.format("主執行緒:等待鎖lock的執行緒數估計:%d, 是否有執行緒正在等待condition:%b,估計等待執行緒數:%d", lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition))); System.out.println("主執行緒:signalAll()"); condition.signalAll(); System.out.println(String.format("主執行緒:等待鎖lock的執行緒數估計:%d, 是否有執行緒正在等待condition:%b,估計等待執行緒數:%d", lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition))); lock.unlock(); }
執行結果如下:
Thread-0: 獲得鎖並進入等待。 Thread-2: 獲得鎖並進入等待。 Thread-3: 獲得鎖並進入等待。 Thread-1: 獲得鎖並進入等待。 Thread-4: 獲得鎖並進入等待。 主執行緒:等待鎖lock的執行緒數估計:0, 是否有執行緒正在等待condition:true,估計等待執行緒數:5 主執行緒:signal() 主執行緒:等待鎖lock的執行緒數估計:1, 是否有執行緒正在等待condition:true,估計等待執行緒數:4 主執行緒:signal() 主執行緒:等待鎖lock的執行緒數估計:2, 是否有執行緒正在等待condition:true,估計等待執行緒數:3 主執行緒:signalAll() 主執行緒:等待鎖lock的執行緒數估計:5, 是否有執行緒正在等待condition:false,估計等待執行緒數:0 Thread-0: 被喚醒並獲得鎖,執行結束退出鎖。 Thread-2: 被喚醒並獲得鎖,執行結束退出鎖。 Thread-3: 被喚醒並獲得鎖,執行結束退出鎖。 Thread-1: 被喚醒並獲得鎖,執行結束退出鎖。 Thread-4: 被喚醒並獲得鎖,執行結束退出鎖。
2.X 參考
Java中Lock,tryLock,lockInterruptibly有什麼區別? - wuxinliulei的回答 - 知乎 https://www.zhihu.com/question/36771163/answer/68974735
當你深入瞭解,你就會發現世界如此廣袤,而你對世界的瞭解則是如此淺薄,請永遠保持謙卑的態度。