JUC之CountDownLatch与CyclicBarrier

1.前言

java.util.concurrent包中为我们提供了很多的线程同步工具类,例如CountDownLatchCyclicBarrier,那么它们主要的用途是什么呢?且看后续分析。

2.CountDownLatch

2.1 什么是CountDownLatch

CountDownLatch,顾名思义,这是一个带计数器的线程同步工具。我们来看官方解释:

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

意味着:CountDownLatch是一个线程同步工具,它允许一个或多个线程进入等待状态,直到其他线程执行完毕后,这些等待的线程才继续往下执行。

这个解释或许对于没有接触过老铁还比较生涩,不方,我们还有实战环节。

2.2 CountDownLatch使用场景

其实,在我们的生活中是有很多场景可以运用CountDownLatch来进行实现的。

这是阳光明媚的一天,部门小伙伴相约春游,滴滴师傅已经达到上车点了,就等各位小伙伴全部上车后,就可以出发了。怎么样,这个场景是不是和CountDownLatch的概念很契合呀,但是我们先用Thread.join()的方式来实现,然后才是我们的CountDownLatch,接下来我们通过代码来实现吧。
JUC之CountDownLatch与CyclicBarrier_第1张图片
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("所有小伙伴全部上车,向着目的地进发");
        }
    }
}

执行结果:

JUC之CountDownLatch与CyclicBarrier_第2张图片

我们使用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();
        }
    }
}

执行结果:

JUC之CountDownLatch与CyclicBarrier_第3张图片

确实,整个代码会简洁许多。这就是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);
}

3.CyclicBarrier

3.1 什么CyclicBarrier

CyclicBarrier:直译的意思是循环屏障,听上去意思是可以当成一个屏障,并且可以重复使用。来看官方的解释:

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.

这意味着,CyclicBarrier是一个线程同步工具,它允许一组线程彼此等待,直到这组线程全部执行完毕。

3.2 CyclicBarrier使用场景

这么看上去,这个概念很契合我们的赛跑比赛啊,运动员在起跑线前等待所有运动员准备完毕后,比赛才会正式开始。

JUC之CountDownLatch与CyclicBarrier_第4张图片

接下来我们开始模拟这个场景:

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() + "跨越起跑线");
        }
    }
}

执行结果:

JUC之CountDownLatch与CyclicBarrier_第5张图片

使用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();

使得计数值重置为初始值,这也是为什么叫循环屏障的原因。

4.CountDownLatch和CyclicBarrier的区别

相信大家已经对CountDownLatchCyclicBarrier有所了解,一起来总结一下它们的区别:

  • CountDownLatch是一个线程或多个线程进行等待其他线程执行完毕,CyclicBarrier是线程之间彼此等待。
  • CountDownLatchawait()方法由需要进入等待状态的线程调用,CyclicBarrierawait()方法由所有参与线程调用
  • CountDownLatch的计数值为0后不可重置,CyclicBarrier的计数值为0后可以重置。

你可能感兴趣的:(java,java)