在JDK的并发包中提供了几个常用的并发工具类:CountDownLatch、CyclicBarrier和Semaphore。这三个是 JUC 中较为常用的同步器,通过它们可以方便地实现很多线程之间协作的功能。
一、CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作。它通过一个计数器来实现,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后等待的线程就可以恢复执行任务。
现在有一个旅行团,有五个人要去旅游,大巴需要5个人同时坐上车才可以出发。
public class CountDownLatchDemo {
/**
* 需要5个人坐车
*/
private static final CountDownLatch COUNT = new CountDownLatch(5);
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
final int ii = i;
new Thread(() -> {
int time = new Random().nextInt(1000);
System.out.println("乘客" + ii + "准备时间:" + time / 1000.0 + "s");
try {
//模拟准备就绪的时间
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("乘客" + ii + "准备就绪");
COUNT.countDown();
System.out.println("乘客" + ii + "上车");
}).start();
}
try {
//有乘客未上车,无法出发
COUNT.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------------出发------------");
}
}
代码运行结果:
乘客2准备时间:0.252s
乘客5准备时间:0.337s
乘客4准备时间:0.36s
乘客3准备时间:0.905s
乘客1准备时间:0.115s
乘客1准备就绪
乘客1上车
乘客2准备就绪
乘客2上车
乘客5准备就绪
乘客5上车
乘客4准备就绪
乘客4上车
乘客3准备就绪
乘客3上车
------------出发------------
可以看到,不管乘客准备时间多久,大巴(主线程)都会等所有乘客就绪之后才会出发,否则不能出发(阻塞)。
二、CyclicBarrier
CyclicBarrier的字面意思是可循环利用(Cyclic)的屏障(Barrier),让一组线程到达一个屏障(或同步点)时被阻塞,直到最后一个线程到达屏障,屏障才会开门,所有被屏障拦截的线程才会继续运行。
现在旅行团的所有成员都到了目的地点,为了保证安全,导游需要五个人同时准备好后才可以进入旅游景点。
public class CyclicBarrierDemo {
/**
* 5个人进旅游景点
*/
private static final CyclicBarrier BARRIER = new CyclicBarrier(5);
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
final int ii = i;
new Thread(() -> {
int time = new Random().nextInt(1000);
System.out.println("乘客" + ii + "准备时间:" + time / 1000.0 + "s");
try {
//模拟准备就绪的时间
Thread.sleep(time);
System.out.println("乘客" + ii + "准备就绪");
BARRIER.await();
System.out.println("乘客" + ii + "进入旅游景点");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
程序运行结果:
乘客4准备时间:0.904s
乘客5准备时间:0.634s
乘客1准备时间:0.964s
乘客2准备时间:0.324s
乘客3准备时间:0.731s
乘客2准备就绪
乘客5准备就绪
乘客3准备就绪
乘客4准备就绪
乘客1准备就绪
乘客1进入旅游景点
乘客5进入旅游景点
乘客2进入旅游景点
乘客4进入旅游景点
乘客3进入旅游景点
我们看到只有所有的人(5个线程)都准备就绪时,乘客才能一起进入旅游景点(barrier放行)。
三、Semaphore
Semaphore是用来控制同时访问特定资源的线程数量,他通过协调各个线程,以保证合理的使用公共资源。
旅游团的成员都到达了旅游景点之后,大巴司机就去停车场停车。
public class SemaphoreDemo {
/**
* 停车场一共有三个车位
*/
private static final Semaphore SEMAPHORE = new Semaphore(3);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
final int ii = i;
new Thread(() -> {
try {
int time = new Random().nextInt(1000) + 2000;
SEMAPHORE.acquire();
System.out.println("车" + ii + "抢到车位,停车:" + time / 1000.0 + "s,剩余车位" + SEMAPHORE.availablePermits());
Thread.sleep(time);
SEMAPHORE.release();
System.out.println("车" + ii + "开走啦,剩余车位" + SEMAPHORE.availablePermits());
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
运行结果:
车7抢到车位,停车:2.735s,剩余车位0
车6抢到车位,停车:2.141s,剩余车位0
车1抢到车位,停车:2.688s,剩余车位0
车6开走啦,剩余车位1
车4抢到车位,停车:2.047s,剩余车位0
车1开走啦,剩余车位1
车3抢到车位,停车:2.408s,剩余车位0
车7开走啦,剩余车位1
车2抢到车位,停车:2.04s,剩余车位0
车4开走啦,剩余车位1
车8抢到车位,停车:2.779s,剩余车位0
车2开走啦,剩余车位1
车5抢到车位,停车:2.729s,剩余车位0
车3开走啦,剩余车位1
车0抢到车位,停车:2.503s,剩余车位0
车8开走啦,剩余车位1
车9抢到车位,停车:2.049s,剩余车位0
车5开走啦,剩余车位1
车0开走啦,剩余车位2
车9开走啦,剩余车位3
可以看到,只有前边有车开走(线程释放),才能继续有车进入抢到车位停车。
四、总结
其实CountDownLatch和CyclicBarrier很相似,都是达到一个数量才能执行操作,但CountDownLatch关注的是其他线程的调用者(本例子指的是主线程main),而CyclicBarrier关注的是其他线程本身,阻塞的线程不同。并且CountDownLatch计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()进行重置操作,重新开始计数。
Semaphore比较好理解,起到一个流控的作用。Semaphore的构造方法传入一个整数,表示permit许可证的意思,只有拿到许可证才能进行接下来的操作,用完之后归还许可证即可。
今天的分享就到此结束啦,喜欢的小伙伴记得点赞呦。
关注公众号JavaGrowUp,下期不迷路,获取更多精彩内容。