在java.util.concurrent
包中为我们提供了很多的线程同步工具类,例如CountDownLatch
与CyclicBarrier
,那么它们主要的用途是什么呢?且看后续分析。
CountDownLatch
,顾名思义,这是一个带计数器的线程同步工具。我们来看官方解释:
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
意味着:CountDownLatch
是一个线程同步工具,它允许一个或多个线程进入等待状态,直到其他线程执行完毕后,这些等待的线程才继续往下执行。
这个解释或许对于没有接触过老铁还比较生涩,不方,我们还有实战环节。
其实,在我们的生活中是有很多场景可以运用CountDownLatch
来进行实现的。
这是阳光明媚的一天,部门小伙伴相约春游,滴滴师傅已经达到上车点了,就等各位小伙伴全部上车后,就可以出发了。怎么样,这个场景是不是和CountDownLatch
的概念很契合呀,但是我们先用Thread.join()
的方式来实现,然后才是我们的CountDownLatch
,接下来我们通过代码来实现吧。
1)Thread.join()实现
主要思路:
1)一个滴滴师傅线程,正在等待所有小伙伴上车,上车后继续运行
2)五个小伙伴线程,相继上车
代码示例:
public class CountDownLatchTest {
@SneakyThrows
public static void main(String[] args) {
DriverThread threadDriver = new DriverThread();
Thread threadA = new Thread(() -> System.out.println(Thread.currentThread().getName() + "已上车"), "张三");
Thread threadB = new Thread(() -> System.out.println(Thread.currentThread().getName() + "已上车"), "张四");
Thread threadC = new Thread(() -> System.out.println(Thread.currentThread().getName() + "已上车"), "张五");
Thread threadD = new Thread(() -> System.out.println(Thread.currentThread().getName() + "已上车"), "张六");
Thread threadE = new Thread(() -> System.out.println(Thread.currentThread().getName() + "已上车"), "张七");
threadDriver.setThreads(List.of(threadA, threadB, threadC, threadD, threadE));
threadDriver.start();
threadA.start();
threadB.start();
threadC.start();
threadD.start();
threadE.start();
}
static class DriverThread extends Thread {
private List<Thread> threads;
public List<Thread> getThreads() {
return threads;
}
public void setThreads(List<Thread> threads) {
this.threads = threads;
}
@SneakyThrows
@Override
public void run() {
System.out.println("滴滴师傅已经达到上车点,请各位小伙伴尽快上车哟");
for (Thread thread : getThreads()) {
thread.join();
}
System.out.println("所有小伙伴全部上车,向着目的地进发");
}
}
}
执行结果:
我们使用Thread.join()的方式,实现了需求,不过大家有没有发现,这种方式其实是相当的麻烦,一个线程需要等待其他的若干个线程执行完毕才能继续往下执行,这个线程就需要持有这些线程的实例,并调用join()方法。那么有没有更优雅的方式呢?
接下来轮到主角CountDownLatch
登场了,话不多说直接开干。
代码示例:
public class CountDownLatchTest {
@SneakyThrows
public static void main(String[] args) {
// 5个小伙伴相约春游
// 构造计数器为5的CountDownLatch,因为我们有5个小伙伴
CountDownLatch countDownLatch = new CountDownLatch(5);
new DriverThread(countDownLatch).start();
CustomRunnable customRunnable = new CustomRunnable(countDownLatch);
new Thread(customRunnable, "张三").start();
new Thread(customRunnable, "张四").start();
new Thread(customRunnable, "张五").start();
new Thread(customRunnable, "张六").start();
new Thread(customRunnable, "张七").start();
}
static class DriverThread extends Thread {
private CountDownLatch countDownLatch;
public DriverThread(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@SneakyThrows
@Override
public void run() {
System.out.println("滴滴师傅已经达到上车点,请各位小伙伴尽快上车哟");
// 使当前线程进入等待状态,直到count = 0 时,唤醒此线程
countDownLatch.await();
System.out.println("所有小伙伴全部上车,向着目的地进发");
}
}
static class CustomRunnable implements Runnable {
private CountDownLatch countDownLatch;
public CustomRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "已上车");
// 将计数器减1,直到count = 0,等待的线程就会继续执行
countDownLatch.countDown();
}
}
}
执行结果:
确实,整个代码会简洁许多。这就是CountDownLatch的用法啦。
接下来我们来分析一下实现代码:
1)CountDownLatch构造方法
创建CountDownLatch,并为计数器设置一个初始值。
public CountDownLatch(int count) {}
2) CountDownLatch.countDown()
目的就是让计数器的数值减1。
public void countDown() {
sync.releaseShared(1);
}
3)CountDownLatch.await()
使得当前线程进入等待状态,直到计数值为0,该线程才会继续向下执行
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
CyclicBarrier
:直译的意思是循环屏障,听上去意思是可以当成一个屏障,并且可以重复使用。来看官方的解释:
A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.
这意味着,CyclicBarrier
是一个线程同步工具,它允许一组线程彼此等待
,直到这组线程全部执行完毕。
这么看上去,这个概念很契合我们的赛跑比赛啊,运动员在起跑线前等待所有运动员准备完毕后,比赛才会正式开始。
接下来我们开始模拟这个场景:
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("所有运动员准备完毕,发令员使用发令枪发出信号,比赛开始。"));
CustomRunnable customRunnable = new CustomRunnable(cyclicBarrier);
new Thread(customRunnable, "张三").start();
new Thread(customRunnable, "张四").start();
new Thread(customRunnable, "张五").start();
new Thread(customRunnable, "张六").start();
new Thread(customRunnable, "张七").start();
}
static class CustomRunnable implements Runnable {
private CyclicBarrier cyclicBarrier;
public CustomRunnable(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@SneakyThrows
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "准备完毕");
// 在屏障前等待
cyclicBarrier.await();
// 所有线程都达到屏障后,继续执行
System.out.println(Thread.currentThread().getName() + "跨越起跑线");
}
}
}
执行结果:
使用CyclicBarrier,可以很方便的模拟赛跑比赛的场景。
接下来我们分析一下代码实现:
1)构造方法
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("所有运动员准备完毕,发令员使用发令枪发出信号,比赛开始。"));
可以结合我们的CyclicBarrier
为我们提供的构造方法
// parties:表示参与的线程个数,barrierAction:表示所有参与线程都达到屏障时,要执行的命令
public CyclicBarrier(int parties, Runnable barrierAction) {}
2)CyclicBarrier.await();
使得先到的线程在屏障前等待,直到所有线程都达到屏障
cyclicBarrier.await();
3)CyclicBarrier.reset();
使得计数值重置为初始值,这也是为什么叫循环屏障的原因。
相信大家已经对CountDownLatch
和CyclicBarrier
有所了解,一起来总结一下它们的区别:
CountDownLatch
是一个线程或多个线程进行等待其他线程执行完毕,CyclicBarrier
是线程之间彼此等待。CountDownLatch
的await()
方法由需要进入等待状态的线程调用,CyclicBarrier
的await()
方法由所有参与线程调用CountDownLatch
的计数值为0后不可重置,CyclicBarrier
的计数值为0后可以重置。