併發編程—併發工具類
這裏將會記錄一些併發工具類:CountDownLatch、CyclicBarrier、Semaphore
CountDownLatch
CountDownLatch是什麼
CountDownLatch是在java1.5被引入的,存在於java.util.concurrent包下。它的作用是當一個線程任務完成後,它必須等待其它的線程的任務執行完成後,主線程才能繼續往下執行。
CountDownLatch是通過一個計數器來實現的,計數器的初始值爲線程的數量。每當一個線程完成了自己的任務後,計數器的值就會減1(這個可以根據業務去確定,也就是不一定就是減1)。當計數器值到達0時,它表示所有的線程已經完成了任務,然後在閉鎖上等待的線程就可以恢復執行任務。
CountDownLatch如何使用
方法說明:
- new CountDownLatch(6):我們實例化CountDownLatch時,需要傳入一個參數[int]count,這個count就是計數器的數值。
- countDown():做減一的操作,每調用一次,計數器的數值就會減1。
- await():使線程阻塞,等待計數器的數值變成0時,放行阻塞的線程。
具體使用:
/**
* 定義
*/
private static CountDownLatch downLatch = new CountDownLatch(6);
/**
* 扣減的線程
*/
static class DownLatchThread implements Runnable {
@Override
public void run() {
// 業務代碼
// countDown 可以理解爲扣減1也就是 -1
downLatch.countDown();
System.out.println("Thread: " + Thread.currentThread().getId());
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 6; i++) {
// 啓動六個線程 也就是說 countDown運行了六次 【-6】
new Thread(new DownLatchThread()).start();
}
// await(), 等待6個線程全部執行完
downLatch.await();
// 打印 可以看出這句話是六個線程運行完成之後才運行的
System.out.println("主線程運行");
}
運行的結果:
Thread: 11
Thread: 12
Thread: 13
Thread: 14
Thread: 15
Thread: 16
主線程運行
從上面的代碼來看我們初始化了CountDownLatch的大小爲6,然後啓動6個線程,這六個線程都調用一次countDown()方法。從結果來看,主線程是等待6個線程運行完之後才執行的,這也體現了CountDownLatch的作用。【如果我啓動五個線程,也就是調用五次countDown()方法,這時線程會一直阻塞。】
CyclicBarrier
CyclicBarrier是什麼
從字面意思上來看CyclicBarrier就是循環(Cyclic)的屏障(Barrier);
作用:讓一組線程達到某個屏障,被阻塞,一直到組內最後一個線程達到屏障時,屏障開放,所有被阻塞的線程會繼續運行。
CyclicBarrier如何使用
方法說明
- new CyclicBarrier(6):實例化CyclicBarrier時傳入參數count,count可以理解爲線程的數量
- new CyclicBarrier(int count, Runnable runnable):當最後一個一組中的最後一個線程任務執行完後,指定一個任務執行
- await():調用的線程阻塞,直到一組線程中的最後一個線程調用await(),所有被awati()方法阻塞的線程都會被放行
- reset():將屏障重置爲其初始狀態。如果所有參與者目前都在屏障處等待,則它們將返回,同時拋出一個BrokenBarrierException。
具體使用:
/**
* 實例化CyclicBarrier
*/
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(6);
/**
* 工作線程
*/
static class BarrierThread implements Runnable {
@Override
public void run() {
try {
// 不是最後一個線程,阻塞
System.out.println("thread: "+ Thread.currentThread().getId() + " await.");
cyclicBarrier.await();
// 只有當最後一個線程的調用await之後,每個線程await()下面的代碼纔會執行
System.out.println("do something .");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 6; i++) {
new Thread(new BarrierThread()).start();
}
System.out.println("主線程運行");
}
運行結果
主線程運行
thread: 13 await.
thread: 11 await.
thread: 12 await.
thread: 14 await.
thread: 15 await.
thread: 16 await.
do something .
do something .
do something .
do something .
do something .
do something .
這段代碼也很簡單,實例化一個大小爲6的CyclicBarrier,啓動6個線程每個線程都調用了await()方法。從打印的結果來看,當線程16運行之前,線程11,12,13,14,15都是阻塞狀態,線程16 執行了await()方法後。所有的線程都被放行,繼續執行await()方法後的代碼。
CyclicBarrier與CountDownLatch比較
- CountDownLatch:一個線程(或者多個),等待另外N個線程完成某個事情之後才能執行;CyclicBarrier:N個線程相互等待,任何一個線程完成之前,所有的線程都必須等待。
- CountDownLatch:一次性的;CyclicBarrier:可以重複使用。
- CountDownLatch基於AQS;CyclicBarrier基於鎖和Condition。本質上都是依賴於volatile和CAS實現的。
Semaphore
Semaphore是什麼
字面意思:信號量
作用:控制同時訪問某個特定資源的線程數量,用在流量控制
Semaphore如何使用
重要方法說明
- new Semaphore(int permits):初始化時,傳入許可證的數量;
- acquire():獲取許可證, -1;
- release():歸還許可證, +1;
- hasQueuedThreads():是否有阻塞的線程;
- getQueueLength():得到阻塞線程的數量;
- availablePermits():可用許可證的數量;
具體使用
/**
* 座位數量10
*/
private final Semaphore seatCount = new Semaphore(10);
/**
* 構造方式私有化
*/
private UseSemaphoreDemo() {
}
private static UseSemaphoreDemo instance = null;
private static UseSemaphoreDemo getInstance() {
if (instance == null) {
synchronized (UseSemaphoreDemo.class) {
instance = new UseSemaphoreDemo();
}
}
return instance;
}
static class WorkThread extends Thread {
UseSemaphoreDemo semaphoreDemo = UseSemaphoreDemo.getInstance();
@Override
public void run() {
try {
// 獲取座位使用權 就是調用 acquire() 方法
semaphoreDemo.seatCount.acquire();
System.out.println("線程Thread: " + Thread.currentThread().getId() +
"獲取到使用權");
if (semaphoreDemo.seatCount.hasQueuedThreads()) {
// 是否有等待的線程
System.out.println("等待的線程數量:" + semaphoreDemo.seatCount.getQueueLength());
}
// 獲取使用權後休眠
Thread.sleep(500 + Thread.currentThread().getId());
// 歸還座位的使用權 調用release()方法
semaphoreDemo.seatCount.release();
System.out.println("線程Thread: " + Thread.currentThread().getId() +
"歸還了使用權");
System.out.println("可用座位:" + semaphoreDemo.seatCount.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 60個線程去獲取座位
for (int i = 0; i < 60; i++) {
new WorkThread().start();
}
}
上面代碼的運行結果
線程Thread: 11獲取到使用權
線程Thread: 12獲取到使用權
線程Thread: 13獲取到使用權
線程Thread: 14獲取到使用權
線程Thread: 15獲取到使用權
線程Thread: 16獲取到使用權
線程Thread: 17獲取到使用權
線程Thread: 18獲取到使用權
線程Thread: 19獲取到使用權
線程Thread: 20獲取到使用權
線程Thread: 11歸還了使用權
線程Thread: 21獲取到使用權
可用座位:0
等待的線程數量:49
線程Thread: 12歸還了使用權
線程Thread: 22獲取到使用權
可用座位:0
等待的線程數量:48
線程Thread: 13歸還了使用權
線程Thread: 23獲取到使用權
...
上面代碼的意思:一共10個座位,60個線程去獲取使用權。
從控制檯打印的結果來看:前10個線程迅速的拿到了座位的使用權,而之後的線程必須要等到拿到使用權的線程歸還使用權後才能得到。這個也說明了Semaphore的作用。