鎖的機制從整體的運行轉態來講核心就是:阻塞,解除阻塞,但是如果僅僅是這點功能,那么JUC並不能稱為一個優秀的線程開發框架,然而是因為在juc里面提供了大量方便的同步工具輔助類。
Semaphore信號量
Semaphore通常用於限制可以訪問某些資源(物理or邏輯)的線程數目。
例如,大家排隊去銀行辦理業務,但是只有兩個銀行窗口提供服務,來了10個人需要辦理業務,所以這10個排隊的人員需要依次使用這兩個業務窗口來辦理業務。
觀察Semaphore類的基本定義:
public class Semaphore extends Object implements Serializable
Semaphore類中定義的方法有如下幾個:
- 構造方法:
public Semaphore(int premits),設置服務的信號量;
- 構造方法:
public Semaphore(int premits,boolean fair) ,是否為公平鎖;
- 等待執行:
public void acquireUninterruptibly(int permits)
- 設置的信號量上如果有阻塞的線程對象存在,那么將一直持續阻塞狀態。
- 釋放線程的阻塞狀態:
public void release(int permits);
- 返回可用的資源個數:
public int availablePermits();
范例:實現銀行排隊業務辦理
package so.strong.mall.concurrent; import java.util.Random; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class SemaphoreDemo { public static void main(String[] args) { final Semaphore semaphore = new Semaphore(2); //現在允許操作的資源一共有2個 final Random random = new Random(); //模擬每一個用戶辦理業務的時間 for (int i = 0; i < 10; i++) { new Thread(new Runnable() { @Override public void run() { //每一個線程就是一個要辦理業務的人員 if (semaphore.availablePermits() > 0) { //現有空余窗口 System.out.println("[" + Thread.currentThread().getName() + "]進入銀行,沒有人辦理業務"); } else { //沒有空余位置 System.out.println("[" + Thread.currentThread().getName() + "]排隊等候辦理業務"); } try { semaphore.acquire(); //從信號量中獲得操作許可 System.out.println("[" + Thread.currentThread().getName() + "]開始辦理業務"); TimeUnit.SECONDS.sleep(random.nextInt(10)); //模擬辦公延遲 System.out.println("[" + Thread.currentThread().getName() + "]結束業務辦理"); semaphore.release(); //當前線程離開辦公窗口 } catch (Exception e) { e.printStackTrace(); } } }, "顧客-" + i).start(); } } }
[顧客-0]進入銀行,沒有人辦理業務 [顧客-0]開始辦理業務 [顧客-1]進入銀行,沒有人辦理業務 [顧客-1]開始辦理業務 [顧客-2]排隊等候辦理業務 [顧客-3]排隊等候辦理業務 [顧客-4]排隊等候辦理業務 [顧客-5]排隊等候辦理業務 [顧客-6]排隊等候辦理業務 [顧客-7]排隊等候辦理業務 [顧客-8]排隊等候辦理業務 [顧客-9]排隊等候辦理業務 [顧客-0]結束業務辦理 [顧客-2]開始辦理業務 [顧客-1]結束業務辦理 [顧客-3]開始辦理業務 [顧客-2]結束業務辦理 [顧客-4]開始辦理業務 [顧客-3]結束業務辦理 [顧客-5]開始辦理業務 [顧客-4]結束業務辦理 [顧客-6]開始辦理業務 [顧客-5]結束業務辦理 [顧客-7]開始辦理業務 [顧客-7]結束業務辦理 [顧客-8]開始辦理業務 [顧客-6]結束業務辦理 [顧客-9]開始辦理業務 [顧客-8]結束業務辦理
這種信號量的處理在實際開發中有什么用呢?例如,現在對於數據庫的連接一共有2個連接,那么可能有10個用戶等待進行數據庫操作,能夠使用的連接個數為2個,這樣10個用戶就需要排隊依次使用這兩個連接來進行數據庫操作。
CountDownLatch閉鎖
CoundDownLatch描述的是一個計數的減少,下面首先觀察一個程序的簡單問題:
范例:編寫一個簡單的多線程開發
package so.strong.mall.concurrent; public class CountDownDemo { public static void main(String[] args) { for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { System.out.println("["+Thread.currentThread().getName()+"]線程應用執行完畢"); } },"線程對象-"+i).start(); } System.out.println("[***主線程***]所有的程序執行完畢"); } }
[***主線程***]所有的程序執行完畢 [線程對象-1]線程應用執行完畢 [線程對象-0]線程應用執行完畢
現在可以發現,對於此時應該保證所有的線程執行完畢后在執行程序的輸出計算,就好比:旅游團集合人員乘車離開。應該保證所有的線程都執行完畢了(指定個數的線程),這樣的話就必須做一個計數處理。
CoundDownLatch這個類能夠使一個線程等待其他線程完成各自的工作后再執行。例如,應用程序的主線程希望在負責啟動框架服務的線程已經啟動所有的框架服務之后再執行。
CoundDownLatch是通過一個計數器來實現的,計數器的初始值為線程的數量。每當一個線程完成了自己的任務后,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然后在閉鎖上等待的線程就可以恢復執行任務。
CoundDownLatch類之中的常用方法有如下幾個:
- 構造方法:
public CountDownLatch(int count); //要設置一個等待的線程個數;
-
減少等待個數:
public void countDown();
-
等待countDownLatch為0:
public void await() throws InterruptedException;
范例:利用CountDownLatch解決之前的設計問題
package so.strong.mall.concurrent; import java.util.concurrent.CountDownLatch; public class CountDownDemo { public static void main(String[] args) throws Exception { final CountDownLatch countDownLatch = new CountDownLatch(2); //2個線程全部執行完畢后可繼續執行 for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { System.out.println("[" + Thread.currentThread().getName() + "]線程應用執行完畢"); countDownLatch.countDown(); //減少等待的線程個數 } }, "線程對象-" + i).start(); } countDownLatch.await(); //等待計數的結束(個數為0) System.out.println("[***主線程***]所有的程序執行完畢"); } }
[線程對象-0]線程應用執行完畢 [線程對象-1]線程應用執行完畢 [***主線程***]所有的程序執行完畢
CyclicBarrier柵欄
CyclicBarrier和CountDownLatch是非常類似的,CyclicBarrier核心的概念是在於設置一個等待線程的數量邊界,到達了此邊界之后進行執行。
CyclicBarrier類是一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點(Common Barrier Point)。
CyclicBarrier類是一種同步機制,它能夠對處理一些算法的線程實現同。換句話講,它就是一個所有線程必須等待的一個柵欄,直到所有線程都到達這里,然后所有線程才可以繼續做其他事情。
通過調用CyclicBarrier對象的await()方法,兩個線程可以實現互相等待。一旦N個線程在等待CyclicBarrier達成,所有線程將被釋放掉去繼續執行。
CyclicBarrier類的主要方法如下:
- 構造方法:
public CyclicBarrier(int parties);//設置等待的邊界;
- 傻傻的等待其他線程:
public int await() throws InterruptedException, BrokenBarrierException;
- 等待其他線程:
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException;
- 重置等待的線程個數:
public void reset();
范例:觀察CyclicBarrier進行等待處理
package so.strong.mall.concurrent; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; public class CyclicBarrierDemo { public static void main(String[] args) throws Exception{ final CyclicBarrier cyclicBarrier = new CyclicBarrier(2); //當湊夠2個線程金進行觸發 for (int i = 0; i < 3 ; i++) { final int second = i; new Thread(new Runnable() { @Override public void run() { System.out.println("["+Thread.currentThread().getName()+"]-等待開始"); try { TimeUnit.SECONDS.sleep(second); cyclicBarrier.await(); //等待處理 } catch (Exception e) { e.printStackTrace(); } System.out.println("["+Thread.currentThread().getName()+"]-等待結束"); } },"娛樂者-"+i).start(); } } }
[娛樂者-0]-等待開始 [娛樂者-1]-等待開始 [娛樂者-2]-等待開始 [娛樂者-1]-等待結束 [娛樂者-0]-等待結束
如果不想一直等待則可以設置超時時間,則超過了等待時間之后將會出現"TimeoutException"。
package so.strong.mall.concurrent; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; public class CyclicBarrierDemo { public static void main(String[] args) throws Exception{ final CyclicBarrier cyclicBarrier = new CyclicBarrier(2); //當湊夠2個線程金進行觸發 for (int i = 0; i < 3 ; i++) { final int second = i; new Thread(new Runnable() { @Override public void run() { System.out.println("["+Thread.currentThread().getName()+"]-等待開始"); try { TimeUnit.SECONDS.sleep(second); cyclicBarrier.await(6,TimeUnit.SECONDS); //等待處理 } catch (Exception e) { e.printStackTrace(); } System.out.println("["+Thread.currentThread().getName()+"]-等待結束"); } },"娛樂者-"+i).start(); } } }
[娛樂者-0]-等待開始 [娛樂者-1]-等待開始 [娛樂者-2]-等待開始 [娛樂者-1]-等待結束 [娛樂者-0]-等待結束 Disconnected from the target VM, address: '127.0.0.1:63717', transport: 'socket' [娛樂者-2]-等待結束 java.util.concurrent.TimeoutException at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250) at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:427) at so.strong.mall.concurrent.CyclicBarrierDemo$1.run(CyclicBarrierDemo.java:21) at java.lang.Thread.run(Thread.java:745)
CyclicBarrier還有一個特點是可以進行重置處理
范例:重置處理
package so.strong.mall.concurrent; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit;
public class CyclicBarrierResetDemo { public static void main(String[] args) throws Exception { final CyclicBarrier cb = new CyclicBarrier(2); //當湊夠2個線程就進行觸發 for (int i = 0; i < 3; i++) { final int second = i; new Thread(new Runnable() { @Override public void run() { System.out.println("[" + Thread.currentThread().getName() + "]-等待開始"); try { if (second == 2) { cb.reset(); //重置 System.out.println("[重置處理****]" + Thread.currentThread().getName()); } else { TimeUnit.SECONDS.sleep(second); cb.await(6,TimeUnit.SECONDS);//等待處理 } } catch (Exception e) { e.printStackTrace(); } System.out.println("[" + Thread.currentThread().getName() + "]-等待結束"); } }, "娛樂者-" + i).start(); } } }
[娛樂者-0]-等待開始 [娛樂者-1]-等待開始 [娛樂者-2]-等待開始 [重置處理****]娛樂者-2 [娛樂者-2]-等待結束 [娛樂者-1]-等待結束 [娛樂者-0]-等待結束
CountDownLatch與CyclicBarrier的區別:
- CountDownLatch最大的特征是進行一個數據減法的操作等待,所有的統計操作一旦開始之中就必須執行countDown()方法,如果等待個數不是0,就被一只等待,並且無法重置。
- CyclicBarrier設置一個等待的臨界點,並且可以有多個等待線程出現,只要滿足了臨界點就觸發了線程的執行代碼后將重新開始進行計數處理操作,也可以直接利用reset()方法執行重置操作。