JUC线程同步类工具CountDownLatch和CyclicBarrier

文章目录

  • 前言
  • 一、CountDownLatch和CyclicBarrier
  • 二、CountDownLatch实战场景
  • 三、CyclicBarrier实战场景
  • 总结


前言

Java5.0在java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。本章主要介绍了两种JUC常用类:CountDownLatch和CyclicBarrier。这两种常常作为线程同步类工具去使用。


一、CountDownLatch和CyclicBarrier

例如:现在有50个任务,这50个任务在完成之后,才能执行下一个方法,要怎么设计?
答:JDK给我们提供的线程工具类,CountDownLatch和CyclicBarrier,这两个类都可以等到线程完成之后,才去执行某些操作。
JUC线程同步类工具CountDownLatch和CyclicBarrier_第1张图片
既然都可以满足需要,那这两个类有什么区别?
答:主要的区别就是CountDownLatch用完了,就结束了,没法复用。而CyclicBarrier不一样,它可以复用。

接下来我们来详细讲解一下两个类的概念。

CountDownLatch:使一个线程或多个线程等待其他线程各自执行完毕后再执行,通过一个计数器来实现的。
计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为 0 时,表示所有线程都执行完毕,等待的线程就可以恢复工作。
举个例子:百米赛跑,4名运动员(子线程)听到后同时起跑,,裁判(主线程)要等所有选手到达终点进行汇总排名。

CyclicBarrier:让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门
举个例子:张三、李四、王五约好一起吃饭,只有三个人都到了才能开始吃。

从上面的描述可以发现两者的等待主体不同

CountDownLatch调用await()通常是主线程/调用线程(让被调用线程先执行完),所以是主线程/调用线程阻塞
CyclicBarrier调用await()是在任务线程调用的(等待其他任务到达),所以是任务线程阻塞

两者有什么共同点:这两个类都是基于AQS实现的(关于AQS之前的文章有详细介绍到)。

当我们在构建CountDownLatch对象时,传入的值其实就会赋值给AQS的关键变量state
执行countDown方法时,其实就是利用CAS 将state 减一
执行await方法时,其实就是判断state是否为0,不为0则加入到队列中,将该线程阻塞掉(除了头结点)
头节点会一直自旋等待state为0,当state为0时,头节点把剩余的在队列中阻塞的节点也一并唤醒。
CyclicBarrier则利用ReentrantLock和Condition,自身维护了count和parties变量(实现复用)。
每次调用await将count-1,并将线程加入到condition队列上。
等到count为0时,则将condition队列的节点移交至AQS队列,并全部释放。

二、CountDownLatch实战场景

百米赛跑,4名运动员(子线程)听到后同时起跑,裁判(主线程)要等所有选手到达终点进行汇总排名。

public class CountdownLatchDemo {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        final CountDownLatch cdAnswer = new CountDownLatch(4);
        for (int i = 0; i < 4; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                       Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("选手" + Thread.currentThread().getName() + "到达终点");
                        cdAnswer.countDown();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            service.execute(runnable);
        }
        try {
            //调用await()通常是主线程/调用线程(让被调用线程先执行完),所以是主线程阻塞
            cdAnswer.await();
            System.out.println("所有选手都到达终点,"+"裁判"+Thread.currentThread().getName()+"汇总成绩排名");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        service.shutdown();
    }
}

JUC线程同步类工具CountDownLatch和CyclicBarrier_第2张图片
在构建CountDownLatch对象时,传入的值其实就会赋值给AQS的关键变量state
在这里插入图片描述
在这里插入图片描述
执行countDown方法时,其实就是利用CAS 将state 减一
在这里插入图片描述


三、CyclicBarrier实战场景

5个人约定好一起吃饭,只有5个人全部到达之后才能开始吃饭,先到达的人必须等待。

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 人员数为5,栅栏需要拦截4位提前到达的人员。
        int memberCount = 5;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(memberCount);
        // 开启5个人员线程
        for(int i = 1; i <= 5; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "进入饭桌");
                    //任务线程调用的(等待其他任务到达),任务线程阻塞
                    cyclicBarrier.await();
                    // 全部的任务线程(5人)全部到达才能开始
                    System.out.println(Thread.currentThread().getName() + "开始吃饭");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

JUC线程同步类工具CountDownLatch和CyclicBarrier_第3张图片
默认的构造方法是CyclicBarrier(int parties),其参数表示屏障将同时拦截的线程数量。
在这里插入图片描述
JUC线程同步类工具CountDownLatch和CyclicBarrier_第4张图片
总结:每个线程使用await()方法告诉CyclicBarrier我已经到达了屏障,然后开始等待。直到parties个参与线程都调用了await方法,那么栅栏将打开,此时所有被栅栏拦截的线程都将继续往下执行,而栅栏将被重置以便下次使用。


总结

JUC线程同步类工具CountDownLatch和CyclicBarrier_第5张图片

本文讲解了CountDownLatch和CyclicBarrier两个线程同步类工具的含义以及应用场景。JUC常用类还有很多,在以后的章节中会介绍更多的JUC常用类供参考学习。


你可能感兴趣的:(并发编程,java,开发语言)