java并发工具类:CountdownLatch,CyclicBarrier,Semaphore
在jdk开发包java.util.concurrent
中提供了几个非常有用的并发工具类:
CountdownLatch
CyclivcBarrier
Semaphore
接下来一一解释其特性和用法:
1.CountdownLatch
1.1介绍
这是jdk文档的相关介绍,简而言之就是:
-
CountDownLatch
是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用),CountDownLatch
能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现,计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。 计数器初始值为线程的数量。这时任何调用这个对象上的await()
方法都会阻塞,进入阻塞队列,如果一个线程调用了countDown()方法,则会使计数器-1;当计数器的值为0时,这时候阻塞队列中调用await()方法的线程便会逐个被唤醒,从而进入后续的操作
1.2CountDownLatch的用法
-
1.某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,计数器减一
countdownLatch.countDown()
,当计数器的值变为0时,在CountDownLatch上await()
的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。 -
2.实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的
CountDownLatch(1)
,将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await()
,当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。
1.3CountDownLatch应用场景
1.3.1主线程等待子线程执行完成在执行:
public class countDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
for(int i = 0;i < 3;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "已完成任务,数量减一");
countDownLatch.countDown();
}).start();
}
System.out.println("主线程开始等待...");
countDownLatch.await(); // 等待计数器归零,然后再向下执行
System.out.println("主线程等待结束,即将关闭...");
}
}
查看输出代码:
主线程开始等待...
Thread-0已完成任务,数量减一
Thread-1已完成任务,数量减一
Thread-2已完成任务,数量减一
主线程等待结束,即将关闭...
很简单的一段代码,await()
使主线程阻塞,当countDownLatch
里的计数为0时,主线程被唤醒,继续执行主线程里的代码,这里的计数次数是根据线程数量设定的,可以自定义,假如设定为4,程序就会一直阻塞,可以设置await()
参数值规定时间 ,时间到后就不再阻塞,比如await(1000,TimeUnit.MILLISECONDS);
在1s后不再阻塞
1.3.2赛跑
public class countDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
for(int i = 1;i < 6;i++){
new Thread(new runner(latch),""+i).start();
}
new Thread(new Boss(latch)).start();
Thread.sleep(2000);
System.out.println("比赛结束");
}
}
class runner implements Runnable{
CountDownLatch countDownLatch;
public runner(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"准备就绪");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出发!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "到达终点");
countDownLatch.countDown();
}
}
class Boss implements Runnable{
CountDownLatch countDownLatch;
public Boss(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("裁判准备吹哨3.2.1...");
countDownLatch.countDown();
}
}
输出结果:
1准备就绪
2准备就绪
3准备就绪
4准备就绪
5准备就绪
裁判准备吹哨3.2.1...
1出发!
3出发!
5出发!
4出发!
2出发!
2到达终点
1到达终点
3到达终点
5到达终点
4到达终点
比赛结束
Process finished with exit code 0
2.CyclicBarrier
2.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是一个同步辅助类,它允许一组线程相互等待直到所有线程都到达一个公共的屏障点。
在程序中有固定数量的线程,这些线程有时候必须等待彼此,这种情况下,使用CyclicBarrier很有帮助。这个屏障之所以用循环修饰,是因为在所有的线程释放彼此之后,这个屏障是可以重新使用的。
我们可以简单的将其理解为一个加法计数器:
- 当累加到额定值(自定义)时,可以完成指定任务,然后归零等待下一次累加到额定值
2.2使用场景及用法
看例子:
public class cyclicDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(30,()->{
System.out.println("人数够了,司机准备发车");
});
for(int i = 1;i <= 30;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "上车");
try {
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + ":发车了,开心");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
查看结果:
Thread-28上车
Thread-29上车
人数够了,司机准备发车
Thread-29:发车了,好开心
Thread-1:发车了,好开心
通过这个例子,我们发现它的用法和CountdownLatch
大同小异,下面介绍常用的几个方法:
- CyclicBarrier(parties):初始化相互等待的线程数量的构造方法。
- CyclicBarrier(parties,Runnable barrierAction):初始化相互等待的线程数量以及屏障线程的构造方法,后者是一个runnable接口,重写run方法就是计数器满了后CyclicBarrier要执行的逻辑
- await():调用这个方法的线程会阻塞
3.Semaphore
为了让大家好理解,可以把Semaphore
理解为一个停车场:只能停规定数量的车,其他的车只有当停车场里的车离开才能进去,举个例子
public class Demo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//规定容量为3
for(int i = 1;i <= 6;i++){
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},""+i).start();
}
}
}
查看结果:
1抢到车位
3抢到车位
2抢到车位
3离开车位
1离开车位
2离开车位
5抢到车位
4抢到车位
6抢到车位
4离开车位
6离开车位
5离开车位
这和我们介绍的一样,我们看一下其中的两个方法:
- semaphore.acquire():获得,假设如果已经满了,等待,等待被释放为止!
- semaphore.release():释放,释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
推荐阅读
-
java并发工具类:CountdownLatch,CyclicBarrier,Semaphore
-
Java并发编程工具类:CountDownLatch、CyclicBarrier、Semaphore
-
并发学习之CountDownLatch、CyclicBarrier以及Semaphore
-
Java并发学习笔记(九):Semaphore、CountdownLatch、CyclicBarrier
-
Java并发6:CountDownLatch &CyclicBarrier &Semaphore
-
并发编程——彻底掌握CountDownLatch,CyclicBarrier和Semaphore
-
Java并发编程系列---Java中的并发工具类CountDownLatch、CyclicBarrier、Semaphore、Exchanger
-
Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解
-
Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解
-
java线程并发cyclicbarrier类使用示例