什麼是 Java Semaphore 和 Mutex – Java Concurrency MultiThread 舉例說明
已發表: 2015-03-12Java並發是一個非常廣泛的話題。 有數百個教程和示例可供使用。 前段時間我寫過一些關於在 Java 中並發運行多個線程和不同類型的同步塊的教程。
在本教程中,我們將介紹:
- 互斥量的解釋
- 信號量的解釋
- 兩個有細節的例子
讓我們開始吧
在閱讀以下說明時Let's keep this in mind
:
- 以 Shopper 和 Customer 為例
- 購物者正在藉筆記本電腦
- 客戶可以來使用筆記本電腦——客戶需要鑰匙才能使用筆記本電腦
- 使用後 – 客戶可以將筆記本電腦退還給購物者
什麼是互斥鎖(只有 1 個線程):
購物者有筆記本電腦的鑰匙。 一位客戶當時可以擁有鑰匙——借用一台筆記本電腦。 當任務完成時,Shopper 將鑰匙交給(釋放)隊列中的下一位顧客。
Official Definition
:
“互斥鎖通常用於序列化對cannot be executed concurrently
的可re-entrant code
部分的訪問。 互斥對像只允許一個線程進入受控部分,迫使試圖訪問該部分的其他線程等待,直到第一個線程退出該部分。”
換句話說: Mutex = Mutually Exclusive Semaphore
什麼是信號量(N個指定線程):
假設現在 Shopper 有 3 台相同的筆記本電腦和 3 個相同的鑰匙。 信號量是free identical Laptop keys
的數量。 信號量計數——鍵的數量——在開始時設置為 3(所有三台筆記本電腦都是空閒的),然後隨著客戶進來,計數值遞減。如果所有筆記本電腦都在使用,即沒有空閒鍵可供使用筆記本電腦,信號量計數為 0。現在,當任何客戶返回筆記本電腦時,信號量增加到 1(一個空閒密鑰),並提供給隊列中的下一個客戶。
Official Definition
:“信號量將共享資源的同時用戶數限制為最大數量。 線程可以請求對資源的訪問(減少信號量),並且可以發出信號表明它們已經完成了對資源的使用(增加信號量)。”
另一個必須閱讀:單例線程安全實例的延遲創建
示例 1:(解釋如下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
package crunchify . com . tutorial ; import java . util . LinkedList ; import java . util . concurrent . Semaphore ; /** * @author Crunchify.com * */ public class CrunchifySemaphoreMutexTutorial { static Object crunchifyLock = new Object ( ) ; static LinkedList <String> crunchifyList = new LinkedList <String> ( ) ; // Semaphore maintains a set of permits. // Each acquire blocks if necessary until a permit is available, and then takes it. // Each release adds a permit, potentially releasing a blocking acquirer. static Semaphore semaphore = new Semaphore ( 0 ) ; static Semaphore mutex = new Semaphore ( 1 ) ; // I'll producing new Integer every time static class CrunchifyProducer extends Thread { public void run ( ) { int counter = 1 ; try { while ( true ) { String threadName = Thread . currentThread ( ) . getName ( ) + counter ++ ; mutex . acquire ( ) ; crunchifyList . add ( threadName ) ; System . out . println ( "Producer is prdoucing new value: " + threadName ) ; mutex . release ( ) ; // release lock semaphore . release ( ) ; Thread . sleep ( 500 ) ; } } catch ( Exception x ) { x . printStackTrace ( ) ; } } } // I'll be consuming Integer every stime static class CrunchifyConsumer extends Thread { String consumerName ; public CrunchifyConsumer ( String name ) { this . consumerName = name ; } public void run ( ) { try { while ( true ) { // acquire lock. Acquires the given number of permits from this semaphore, blocking until all are // available // process stops here until producer releases the lock semaphore . acquire ( ) ; // Acquires a permit from this semaphore, blocking until one is available mutex . acquire ( ) ; String result = "" ; for ( String value : crunchifyList ) { result = value + "," ; } System . out . println ( consumerName + " consumes value: " + result + "crunchifyList.size(): " + crunchifyList . size ( ) + "\n" ) ; mutex . release ( ) ; } } catch ( Exception e ) { e . printStackTrace ( ) ; } } } public static void main ( String [ ] args ) { new CrunchifyProducer ( ) . start ( ) ; new CrunchifyConsumer ( "Crunchify" ) . start ( ) ; new CrunchifyConsumer ( "Google" ) . start ( ) ; new CrunchifyConsumer ( "Yahoo" ) . start ( ) ; } } |
在上面的教程CrunchifySemaphoreMutexTutorial.java
,當CrunchifyProducer
將threadName
添加到crunchifyList
linkedList 對象時,它可以發出信號量信號。
然後, CrunchifyConsumer
可以嘗試獲取信號量,因此它們將一直等待,直到 CrunchifyProducer 發出已添加線程 ID 的信號。 在發出添加數據的信號後,其中一個消費者將被喚醒,它會知道它可以讀取一個 crunchifyList 對象。 它可以讀取一個列表,然後返回嘗試獲取信號量。
如果在那個時候生產者已經寫入了另一個數據包,它會再次發出信號,然後任何一個消費者將繼續讀取另一個數據包,依此類推……
換一種說法:
1 2 3 |
CrunchifyProducer : Add an object o List - Semaphore . release ( 1 ) CrunchifyConsumer x N ) - Semaphore . acquire ( 1 ) - Read an object from List |

結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Producer is prdoucing new value : Thread - 01 Crunchify consumes value : Thread - 01 , crunchifyList . size ( ) : 1 Producer is prdoucing new value : Thread - 02 Google consumes value : Thread - 02 , crunchifyList . size ( ) : 2 Producer is prdoucing new value : Thread - 03 Yahoo consumes value : Thread - 03 , crunchifyList . size ( ) : 3 Producer is prdoucing new value : Thread - 04 Crunchify consumes value : Thread - 04 , crunchifyList . size ( ) : 4 . . . . . . . . . . . . . . . |
如何防止競態條件:
What if you have multiple Consumers?
在上面的 Java 教程中,消費者(不是生產者)在讀取數據包時(而不是在獲取信號量時)應該鎖定緩衝區,以防止出現競爭條件。 在下面的示例中,生產者還鎖定了列表,因為所有內容都在同一個 JVM 上。
示例 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package crunchify . com . tutorial ; import java . util . concurrent . Semaphore ; /** * @author Crunchify.com * */ public class CrunchifyJavaSemaphoreTutorial { private static final int MAX_CONCURRENT_THREADS = 2 ; private final Semaphore crunchifyAdminLOCK = new Semaphore ( MAX_CONCURRENT_THREADS , true ) ; public void crunchifyStartTest ( ) { for ( int i = 0 ; i < 10 ; i ++ ) { CrunchifyPerson person = new CrunchifyPerson ( ) ; person . start ( ) ; } } public class CrunchifyPerson extends Thread { @Override public void run ( ) { try { // Acquire Lock crunchifyAdminLOCK . acquire ( ) ; } catch ( InterruptedException e ) { System . out . println ( "received InterruptedException" ) ; return ; } System . out . println ( "Thread " + this . getId ( ) + " start using Crunchify's car - Acquire()" ) ; try { sleep ( 1000 ) ; } catch ( Exception e ) { } finally { // Release Lock crunchifyAdminLOCK . release ( ) ; } System . out . println ( "Thread " + this . getId ( ) + " stops using Crunchify's car - Release()\n" ) ; } } public static void main ( String [ ] args ) { CrunchifyJavaSemaphoreTutorial test = new CrunchifyJavaSemaphoreTutorial ( ) ; test . crunchifyStartTest ( ) ; } } |
結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
Thread 11 start using Crunchify 's car - Acquire() Thread 10 start using Crunchify' s car - Acquire ( ) Thread 10 stops using Crunchify 's car - Release() Thread 12 start using Crunchify' s car - Acquire ( ) Thread 13 start using Crunchify 's car - Acquire() Thread 11 stops using Crunchify' s car - Release ( ) Thread 13 stops using Crunchify 's car - Release() Thread 15 start using Crunchify' s car - Acquire ( ) Thread 14 start using Crunchify 's car - Acquire() Thread 12 stops using Crunchify' s car - Release ( ) Thread 14 stops using Crunchify 's car - Release() Thread 16 start using Crunchify' s car - Acquire ( ) Thread 15 stops using Crunchify 's car - Release() Thread 17 start using Crunchify' s car - Acquire ( ) Thread 17 stops using Crunchify 's car - Release() Thread 18 start using Crunchify' s car - Acquire ( ) Thread 19 start using Crunchify 's car - Acquire() Thread 16 stops using Crunchify' s car - Release ( ) Thread 18 stops using Crunchify 's car - Release() Thread 19 stops using Crunchify' s car - Release ( ) |