今天说说Java并发编程辅助类CountDownLatch、CyclicBarrier、Semaphore。
1、CountDownLatch
java.util.concurrent.CountDownLatch
(1)官网英文解释
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
CountDownLatch是一个同步辅助工具,允许一些线程一直等待直到其他线程执行完毕。
A CountDownLatch is initialized with a given count. The await methods block until the current count reaches zero due to invocations of the countDown() method, after which all waiting threads are released and any subsequent invocations of await return immediately. This is a one-shot phenomenon -- the count cannot be reset. If you need a version that resets the count, consider using a CyclicBarrier.
CountDownLatch会被初始化为一个count值,await方法会阻塞直到count变为0,调用countDown方法会使count递减。当count变为0时,所有waiting的线程会被唤醒。
A CountDownLatch initialized with a count of one serves as a simple on/off latch, or gate: all threads invoking await wait at the gate until it is opened by a thread invoking countDown(). A CountDownLatch initialized to N can be used to make one thread wait until N threads have completed some action, or some action has been completed N times.
当CountDownLatch被初始化为1时,就像一个开关。当CountDownLatch被初始化为N时,。。。。
(2)CountDownLatch用法一
public class Driver {
public static void main(String[] args) throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);//1
CountDownLatch doneSignal = new CountDownLatch(4);//N
for (int i = 0; i < 4; i++) {
new Thread(new Worker(i, startSignal, doneSignal)).start();
}
System.out.println("---main thread begin---");
startSignal.countDown();
doneSignal.await();
System.out.println("---main thread end---");
}
}
class Worker implements Runnable {
int threadId;
CountDownLatch startSignal;
CountDownLatch doneSignal;
public Worker(int threadId, CountDownLatch startSignal, CountDownLatch doneSignal) {
this.threadId = threadId;
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
@Override
public void run() {
try {
startSignal.await();
doWork();
doneSignal.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
private void doWork() {
System.out.println("threadId:" + threadId + ", do work...");
}
}
以上代码输出结果:
---main thread begin---
threadId:1, do work...
threadId:2, do work...
threadId:3, do work...
threadId:0, do work...
---main thread end---
(3)CountDownLatch用法二
public class Driver2 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch doneSignal = new CountDownLatch(4);//N
Executor e = Executors.newCachedThreadPool();
for (int i = 0; i < 4; i++) {
e.execute(new WorkerRunnable(i, doneSignal));
}
doneSignal.await();
System.out.println("---main thread end---");
}
}
class WorkerRunnable implements Runnable {
int threadId;
CountDownLatch doneSignal;
public WorkerRunnable(int threadId, CountDownLatch doneSignal) {
this.threadId = threadId;
this.doneSignal = doneSignal;
}
@Override
public void run() {
try {
doWork();
doneSignal.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
private void doWork() {
System.out.println("threadId:" + threadId + ", do work...");
}
}
以上代码输出结果:
threadId:0, do work...
threadId:3, do work...
threadId:2, do work...
threadId:1, do work...
---main thread end---
官方英文文档:
Another typical usage would be to divide a problem into N parts, describe each part with a Runnable that executes that portion and counts down on the latch, and queue all the Runnables to an Executor. When all sub-parts are complete, the coordinating thread will be able to pass through await.
将一个问题分成N个部分,当所有部分执行完毕,等待的线程被唤醒。
2、CyclicBarrier
java.util.concurrent.CyclicBarrier(循环栅栏)
(1)英文官方文档:
A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is called cyclic because it can be re-used after the waiting threads are released.
CyclicBarrier可以让一组线程互相等待到达一个共同的barrier点,CyclicBarrier常用于一组线程互相等待的场景中。当所有等待的线程释放后,CyclicBarrier能够重复使用。
(2)CyclicBarrier构造函数
CyclicBarrier(int parties, Runnable barrierAction)
//Creates a new CyclicBarrier that will trip when the given number of parties (threads) are waiting upon it, and which will execute the given barrier action when the barrier is tripped, performed by the last thread entering the barrier.
CyclicBarrier(int parties)
//Creates a new CyclicBarrier that will trip when the given number of parties (threads) are waiting upon it, and does not perform a predefined action when the barrier is tripped.
说明:参数parties指等待多少个线程至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。
(3)CyclicBarrier重要函数说明
await()
//Waits until all parties have invoked await on this barrier.
//用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;
await(long timeout, TimeUnit unit)
//Waits until all parties have invoked await on this barrier, or the specified waiting time elapses.
(4)CyclicBarrier使用原理伪代码
class Solver {
final int N;
final float[][] data;
final CyclicBarrier barrier;
class Worker implements Runnable {
int myRow;
Worker(int row) { myRow = row; }
public void run() {
while (!done()) {
processRow(myRow);
try {
barrier.await();
} catch (InterruptedException ex) {
return;
} catch (BrokenBarrierException ex) {
return;
}
}
}
}
public Solver(float[][] matrix) {
data = matrix;
N = matrix.length;
barrier = new CyclicBarrier(N,
new Runnable() {
public void run() {
mergeRows(...);
}
});
for (int i = 0; i < N; ++i)
new Thread(new Worker(i)).start();
waitUntilDone();
}
}
(5)CyclicBarrier使用举例1
public class CyclicBarrier1 {
public static void main(String[] args) {
int N = 4;
CyclicBarrier cyclicBarrier = new CyclicBarrier(N);
for (int i = 0; i < N; i++) {
new Worker(cyclicBarrier).start();
}
}
static class Worker extends Thread {
CyclicBarrier cyclicBarrier;
public Worker(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":开始写");
try {
Thread.sleep(3000);//睡眠来模拟写过程
System.out.println(Thread.currentThread().getName() + ":写入完毕,等待其他线程写入完毕");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("所有线程写入完毕," + Thread.currentThread().getName() + ":开始做其他事");
}
}
}
输出结果如下:
Thread-1:开始写
Thread-3:开始写
Thread-2:开始写
Thread-0:开始写
Thread-1:写入完毕,等待其他线程写入完毕
Thread-0:写入完毕,等待其他线程写入完毕
Thread-3:写入完毕,等待其他线程写入完毕
Thread-2:写入完毕,等待其他线程写入完毕
所有线程写入完毕,Thread-2:开始做其他事
所有线程写入完毕,Thread-3:开始做其他事
所有线程写入完毕,Thread-0:开始做其他事
所有线程写入完毕,Thread-1:开始做其他事
说明:
假如有若干个线程都要进行写数据操作,并且只有所有线程都完成写数据操作之后,这些线程才能继续做后面的事情,此时就可以利用CyclicBarrier了。
(6)CyclicBarrier使用举例2
public class CyclicBarrier2 {
public static void main(String[] args) {
int N = 4;
CyclicBarrier cyclicBarrier = new CyclicBarrier(N, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":执行Runnable-barrierAction");
}
});
for (int i = 0; i < N; i++) {
new Worker(cyclicBarrier).start();
}
}
static class Worker extends Thread {
CyclicBarrier cyclicBarrier;
public Worker(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":开始写");
try {
Thread.sleep(3000);//睡眠来模拟写过程
System.out.println(Thread.currentThread().getName() + ":写入完毕,等待其他线程写入完毕");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("所有线程写入完毕," + Thread.currentThread().getName() + ":开始做其他事");
}
}
}
输出结果如下:
Thread-2:开始写
Thread-1:开始写
Thread-3:开始写
Thread-0:开始写
Thread-1:写入完毕,等待其他线程写入完毕
Thread-0:写入完毕,等待其他线程写入完毕
Thread-2:写入完毕,等待其他线程写入完毕
Thread-3:写入完毕,等待其他线程写入完毕
Thread-3:执行Runnable-barrierAction
所有线程写入完毕,Thread-3:开始做其他事
所有线程写入完毕,Thread-2:开始做其他事
所有线程写入完毕,Thread-0:开始做其他事
所有线程写入完毕,Thread-1:开始做其他事
说明:当四个线程都到达barrier状态后,会从四个线程中选择一个线程去执行Runnable。这个被选择的线程是最后一个达到barrier状态的线程,如上面的Thread-3。
3、Semaphore
java.util.concurrent.Semaphore(信号量)
Semaphore可以控制同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
假如一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现。
public class Semaphore1 {
public static void main(String[] args) {
int N = 8;//8个人
Semaphore semaphore = new Semaphore(5);//5个机器
for (int i = 0; i < N; i++) {
new Worker(i, semaphore).start();
}
}
static class Worker extends Thread {
int num;
Semaphore semaphore;
public Worker(int num, Semaphore semaphore) {
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("线程" + num + "获得许可");
Thread.sleep(2000);
System.out.println("线程" + num + "-释放许可。。。");
semaphore.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
输出结果如下:
线程1获得许可
线程3获得许可
线程2获得许可
线程0获得许可
线程4获得许可
线程3-释放许可。。。
线程0-释放许可。。。
线程2-释放许可。。。
线程1-释放许可。。。
线程6获得许可
线程5获得许可
线程4-释放许可。。。
线程7获得许可
线程6-释放许可。。。
线程7-释放许可。。。
线程5-释放许可。。。
4、总结
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
Semaphore和锁有点类似。