• Java并发之CountDownLatch、CyclicBarrier和Semaphore


    CountDownLatch 是能使一组线程等另一组线程都跑完了再继续跑;CyclicBarrier 能够使一组线程在一个时间点上达到同步,可以是一起开始执行全部任务或者一部分任务。

    这次说一下 JUC 中的同步器三个主要的成员:CountDownLatch、CyclicBarrier 和 Semaphore(不知道有没有初学者觉得这三个的名字不太好记)。这三个是 JUC 中较为常用的同步器,通过它们可以方便地实现很多线程之间协作的功能。(下面的代码出自 JDK 文档)

    CountDownLatch

    直译过来就是倒计数(CountDown)门闩(Latch)。倒计数不用说,门闩的意思顾名思义就是阻止前进。在这里就是指 CountDownLatch.await() 方法在倒计数为0之前会阻塞当前线程。

    作用

    CountDownLatch 的作用和 Thread.join() 方法类似,可用于一组线程和另外一组线程的协作。例如,主线程在做一项工作之前需要一系列的准备工作,只有这些准备工作都完成,主线程才能继续它的工作。这些准备工作彼此独立,所以可以并发执行以提高速度。在这个场景下就可以使用 CountDownLatch 协调线程之间的调度了。在直接创建线程的年代(Java 5.0 之前),我们可以使用 Thread.join()。在 JUC 出现后,因为线程池中的线程不能直接被引用,所以就必须使用 CountDownLatch 了。

    示例

    下面的这个例子可以理解为 F1 赛车的维修过程,只有 startSignal (可以表示停车,可能名字不太贴合)命令下达之后,维修工才开始干活,只有等所有工人完成工作之后,赛车才能继续。

    1. class Driver { // ...  
    2.     void main() throws InterruptedException {  
    3.         CountDownLatch startSignal = new CountDownLatch(1);  
    4.         CountDownLatch doneSignal = new CountDownLatch(N);  
    5.  
    6.         for (int i = 0; i < N; ++i) // create and start threads  
    7.             new Thread(new Worker(startSignal, doneSignal)).start();  
    8.  
    9.         doSomethingElse();            // don't let run yet  
    10.         startSignal.countDown();      // let all threads proceed  
    11.         doSomethingElse();  
    12.         doneSignal.await();           // wait for all to finish  
    13.     }  
    14. }  
    15.  
    16. class Worker implements Runnable {  
    17.     private final CountDownLatch startSignal;  
    18.     private final CountDownLatch doneSignal;  
    19.     Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {  
    20.         this.startSignal = startSignal;  
    21.         this.doneSignal = doneSignal;  
    22.     }  
    23.     public void run() {  
    24.         try {  
    25.             startSignal.await();  
    26.             doWork();  
    27.             doneSignal.countDown();  
    28.         } catch (InterruptedException ex) {} // return;  
    29.     }  
    30.  
    31.     void doWork() { ... }  

    当 startSignal.await() 会阻塞线程,当 startSignal.countDown() 被调用之后,所有 Worker 线程开始执行 doWork() 方法,所以 Worker。doWork() 是几乎同时开始执行的。当 Worker.doWork() 执行完毕后,调用 doneSignal.countDown(),在所有 Worker 线程执行完毕之后,主线程继续执行。

    CyclicBarrier

    CyclicBarrier 翻译过来叫循环栅栏、循环障碍什么的(还是有点别扭的。所以还是别翻译了,只可意会不可言传啊)。它主要的方法就是一个:await()。await() 方法没被调用一次,计数便会减少1,并阻塞住当前线程。当计数减至0时,阻塞解除,所有在此 CyclicBarrier 上面阻塞的线程开始运行。在这之后,如果再次调用 await() 方法,计数就又会变成 N-1,新一轮重新开始,这便是 Cyclic 的含义所在。

    CyclicBarrier 的使用并不难,但需要主要它所相关的异常。除了常见的异常,CyclicBarrier.await() 方法会抛出一个独有的 BrokenBarrierException。这个异常发生在当某个线程在等待本 CyclicBarrier 时被中断或超时或被重置时,其它同样在这个 CyclicBarrier 上等待的线程便会受到 BrokenBarrierException。意思就是说,同志们,别等了,有个小伙伴已经挂了,咱们如果继续等有可能会一直等下去,所有各回各家吧。

    CyclicBarrier.await() 方法带有返回值,用来表示当前线程是第几个到达这个 Barrier 的线程。

    和 CountDownLatch 一样,CyclicBarrier 同样可以可以在构造函数中设定总计数值。与 CountDownLatch 不同的是,CyclicBarrier 的构造函数还可以接受一个 Runnable,会在 CyclicBarrier 被释放时执行。

    import java.util.Random;
    import java.util.concurrent.CyclicBarrier;
    
    /** *//**
     * CyclicBarrier类似于CountDownLatch也是个计数器,
     * 不同的是CyclicBarrier数的是调用了CyclicBarrier.await()进入等待的线程数,
     * 当线程数达到了CyclicBarrier初始时规定的数目时,所有进入等待状态的线程被唤醒并继续。
     * CyclicBarrier就象它名字的意思一样,可看成是个障碍,
     * 所有的线程必须到齐后才能一起通过这个障碍。
     * CyclicBarrier初始时还可带一个Runnable的参数,
     * 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。
     */
    public class CyclicBarrierTest {
    
        public static class ComponentThread implements Runnable {
            CyclicBarrier barrier;// 计数器
            int ID;    // 组件标识
            int[] array;    // 数据数组
    
            // 构造方法
            public ComponentThread(CyclicBarrier barrier, int[] array, int ID) {
                this.barrier = barrier;
                this.ID = ID;
                this.array = array;
            }
    
            public void run() {
                try {
                    array[ID] = new Random().nextInt(100);
                    System.out.println("Component " + ID + " generates: " + array[ID]);
                    // 在这里等待Barrier处
                    System.out.println("Component " + ID + " sleep");
                    barrier.await();
                    System.out.println("Component " + ID + " awaked");
                    // 计算数据数组中的当前值和后续值
                    int result = array[ID] + array[ID + 1];
                    System.out.println("Component " + ID + " result: " + result);
                } catch (Exception ex) {
                }
            }
        }
        /** *//**
         * 测试CyclicBarrier的用法
         */
        public static void testCyclicBarrier() {
            final int[] array = new int[3];
            CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {
                // 在所有线程都到达Barrier时执行
                public void run() {
                    System.out.println("testCyclicBarrier run");
                    array[2] = array[0] + array[1];
                }
            });
    
            // 启动线程
            new Thread(new ComponentThread(barrier, array, 0)).start();
            new Thread(new ComponentThread(barrier, array, 1)).start();
        }
    
        public static void main(String[] args) {
            CyclicBarrierTest.testCyclicBarrier();
        }
    }

    三、Semaphore 信号量

    Semaphore翻译成字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

    下面通过一个例子来看一下Semaphore的具体使用:

      假若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:

    public class Test {
        public static void main(String[] args) {
            int N = 8;            //工人数
            Semaphore semaphore = new Semaphore(5); //机器数目
            for(int i=0;i<N;i++)
                new Worker(i,semaphore).start();
        }
         
        static class Worker extends Thread{
            private int num;
            private Semaphore semaphore;
            public Worker(int num,Semaphore semaphore){
                this.num = num;
                this.semaphore = semaphore;
            }
             
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println("工人"+this.num+"占用一个机器在生产...");
                    Thread.sleep(2000);
                    System.out.println("工人"+this.num+"释放出机器");
                    semaphore.release();           
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 相关阅读:
    Windows2012修改光驱盘符
    推薦幾個海外片源站
    Learning Puppet — Variables, Conditionals, and Facts
    红灯是否可以掉头?
    Windows Server 2008
    Learning Puppet — Resource Ordering
    Learning Puppet — Manifests
    Learning Puppet — Resources and the RAL
    quotas and disk replace on netapp
    美国夏令时与冬令时的区别
  • 原文地址:https://www.cnblogs.com/bendantuohai/p/4657275.html
Copyright © 2020-2023  润新知