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
0、JAVA多線程編程
Java多線程編程所涉及的知識點包含線程創建、線程同步、線程間通信、線程死鎖、線程控制(掛起、停止和恢復)。之前
-
篇0
-
篇1
-
篇2
-
篇3
-
篇4
-
篇5
-
篇6
-
篇7
-
篇8