CountDownLatch、Semaphore、CyclicBarrier

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/aogujianhanjianming/article/details/101128191

CountDownLatch

CountDownLatch是一个计数器闭锁,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成。CountDownLatch用一个给定的计数器来初始化,该计数器的操作是原子操作,即同时只能有一个线程去操作该计数器。调用该类await方法的线程会一直处于阻塞状态,直到其他线程调用countDown方法使当前计数器的值变为零,每次调用countDown计数器的值减1。当计数器值减至零时,所有因调用await()方法而处于等待状态的线程就会继续往下执行。这种现象只会出现一次,因为计数器不能被重置,如果业务上需要一个可以重置计数次数的版本,可以考虑使用CycliBarrier。

在某些业务场景中,程序执行需要等待某个条件完成后才能继续执行后续的操作;典型的应用如并行计算,当某个处理的运算量很大时,可以将该运算任务拆分成多个子任务,等待所有的子任务都完成之后,父任务再拿到所有子任务的运算结果进行汇总。

    /**
     * 1、某个线程需要等待其他线程执行完
     */
    public void m1() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(15);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 15; i++) {
            int finalI = i;
            executorService.execute(()->{
                try {
                    TimeUnit.SECONDS.sleep(5);
                    System.out.println(finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        // 等待其他线程执行完才执行
        countDownLatch.await();
        // await也可以传入等待时间,等待时间到了就会往后执行
//        countDownLatch.await(2, TimeUnit.SECONDS);
        System.out.println("全部结束");
        executorService.shutdown();
    }
    /**
     * 2、让线程都准备好一起执行
     * @throws Exception
     */
    public void m2() throws Exception {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        CountDownLatch await = new CountDownLatch(15);

        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i< 15; i++) {
            executorService.execute(new MyRunnable(countDownLatch, await));
        }

        System.out.println("主线程处理自己事情");
        Thread.sleep(3000);
        System.out.println("主线程处理结束");
        // countDownLatch由1变为0,所有等待的15个线程开始执行
        countDownLatch.countDown();

        // 等待15个线程都执行完毕再执行
        await.await();
        System.out.println("子线程处理完毕啦");

        executorService.shutdown();
    }


    class MyRunnable implements Runnable {

        private CountDownLatch countDownLatch;

        private CountDownLatch await;

        public MyRunnable(CountDownLatch countDownLatch, CountDownLatch await) {
            this.countDownLatch = countDownLatch;
            this.await = await;
        }

        @Override
        public void run() {
            try {
                // 等待,countDownLatch由1变为0再执行
                countDownLatch.await();
                System.out.println("子线程" +Thread.currentThread().getName()+ "处理自己事情");
                Thread.sleep(1000);
                await.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

Semaphore

Semaphore与CountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。它也被更多地用来限制流量,类似阀门的 功能。如果限定某些资源最多有N个线程可以访问,那么超过N个主不允许再有线程来访问,同时当现有线程结束后,就会释放,然后允许新的线程进来。有点类似于锁的lock与 unlock过程。相对来说他也有两个主要的方法:

用于获取权限的acquire(),其底层实现与CountDownLatch.countdown()类似;
用于释放权限的release(),其底层实现与acquire()是一个互逆的过程。

   /**
     * 每次最多有3个线程获取许可,每个线程在5秒钟内尝试获取1个许可,获取到的则执行,没有获取到的则不执行,
     * 线程执行用时2秒,那么每次有3个线程同时执行,可以执行3次
     * (首先有3个线程获取到许可,执行完后(2秒)许可释放,接着又有3个线程获取到许可,同样执行完后(2秒)许可释放,
     * 又有3个线程获取到许可,同样执行完后(2秒)许可释放,已经超过了等待许可的时间(5秒),因此后面不再有线程执行)
     */
    public void method4() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 每次最多3个线程获取许可
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            executorService.execute(() -> {
                try {
                    if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) { // 在一定的时间内尝试获取1个许可
                        test1(finalI);
                        semaphore.release(); // 释放1个许可
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }

    /**
     * 每次最多有3个线程获取许可,每个线程尝试获取1个许可,获取到的则执行,没有获取到的则不执行,那么就只有3个线程执行
     */
    public void method3() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 每次最多3个线程获取许可
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            executorService.execute(() -> {
                try {
                    if (semaphore.tryAcquire()) { // 尝试获取1个许可
                        test1(finalI);
                        semaphore.release(); // 释放1个许可
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }

    /**
     * 每次最多有3个线程获取许可,每个线程执行时获取3个许可,那么每次有1个线程执行
     */
    public void method2() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 每次最多3个线程获取许可
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire(3);  // 获取多个许可
                    test1(finalI);
                    semaphore.release(3);  // 释放多个许可
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }

    /**
     * 每次最多有3个线程获取许可,每个线程执行时获取1个许可,那么每次有3个线程同时执行
     */
    public void method1() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 每次最多3个线程获取许可
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();  // 获取1个许可
                    test1(finalI);
                    semaphore.release();  // 释放1个许可
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }

    public void test1(int i) throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        System.out.println(i + "--" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS")));
    }

CyclicBarrier

CyclicBarrier也是一个同步辅助类,它允许一组线程相互等待,直到到达某个公共屏障点(common barrier point)。通过它可以完成多个线程之间相互等待,只有当每个线程都准备就绪后,才能各自继续往下执行后面的操作。类似于CountDownLatch,它也是通过计数器来实现的。当某个线程调用await方法时,该线程进入等待状态,且计数器加1,当计数器的值达到设置的初始值时,所有因调用await进入等待状态的线程被唤醒,继续执行后续操作。因为CycliBarrier在释放等待线程后可以重用,所以称为循环barrier。CycliBarrier支持一个可选的Runnable,在计数器的值到达设定值后(但在释放所有线程之前),该Runnable运行一次,注,Runnable在每个屏障点只运行一个。

使用场景类似于CountDownLatch与CountDownLatch的区别

  • CountDownLatch主要是实现了1个或N个线程需要等待其他线程完成某项操作之后才能继续往下执行操作,描述的是1个线程或N个线程等待其他线程的关系。CyclicBarrier主要是实现了多个线程之间相互等待,直到所有的线程都满足了条件之后各自才能继续执行后续的操作,描述的多个线程内部相互等待的关系。
  • CountDownLatch是一次性的,而CyclicBarrier则可以被重置而重复使用。
    /**
     * 一组线程相互等待,达到5个时开始执行
     * @throws InterruptedException
     */
    public void method3() throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        CyclicBarrier barrier = new CyclicBarrier(5, () -> {  // 指定一个线程,当达到5个开始执行时,会先执行该线程
            System.out.println("callback is running");
        });

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            TimeUnit.SECONDS.sleep(1);  // 每1秒加入一个线程,达到5个时,一起执行
            executorService.execute(() -> {
                try {
                    System.out.println(finalI + "--" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS")) + "--ready" );
                    barrier.await();
                    test1(finalI);
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }


    /**
     * 一组线程相互等待,达到5个时开始执行
     * @throws InterruptedException
     */
    public void method2() throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        CyclicBarrier barrier = new CyclicBarrier(5);

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            TimeUnit.SECONDS.sleep(1);  // 每1秒加入一个线程,达到5个时,一起执行
            executorService.execute(() -> {
                try {
                    System.out.println(finalI + "--" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS")) + "--ready" );
                    barrier.await(2, TimeUnit.SECONDS);  // 在一定时间内等待,超过该时间,没有达到5个,跑出TimeoutException
                    test1(finalI);
                } catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }

    /**
     * 一组线程相互等待,达到5个时开始执行
     * @throws InterruptedException
     */
    public void method1() throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        CyclicBarrier barrier = new CyclicBarrier(5);

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            TimeUnit.SECONDS.sleep(1);  // 每1秒加入一个线程,达到5个时,一起执行
            executorService.execute(() -> {
                try {
                    System.out.println(finalI + "--" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS")) + "--ready" );
                    barrier.await();
                    test1(finalI);
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }

    public void test1(int i) throws InterruptedException {
//        TimeUnit.SECONDS.sleep(1);
        System.out.println(i + "--" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS")));
    }

猜你喜欢

转载自blog.csdn.net/aogujianhanjianming/article/details/101128191