CountDownLatch(闭锁)的简单使用

版权声明:如果您觉得此文有用或对您有帮助,请关注我吧! https://blog.csdn.net/fanrenxiang/article/details/79873676

CountDownLatch简介

有这么一种场景,某个线程需要等待其他线程任务完成后才能继续执行,比如,需要统计计算三个sheet页面上的数据,于是我就需要开启三个线程来做这个事情,一个线程计算一个sheet页,最后三个线程都计算完了再汇总结果。java.util.concurrent包下的CountDownLatch允许一个或多个线程等待,直到其它线程组操作完成(就是N计数器变为0)后才执行,便可以解决这个问题。

CountDownLatch在业界也被称为闭锁,它通过接收一个带int值N的形参构造器来作为计数器,每调用一次CountDownLatch.countDown()方法N就会递减1,同时CountDownLatch.await()方法会阻塞当前线程,直到N=0为止。计数器是一次性行为,不能被重置,若需要重置count,则考虑使用CyclicBarrier。

注意:如下代码可以看到,N的值必须大于0,可以等于0(说明计数器就为0,此时调用await()方法不会阻塞当前线程),同时要保证计数器N在线程通过之前就先调用。

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

CountDownLatch使用demo

final CountDownLatch countdown = new CountDownLatch(1);
for (int i = 0; i < 10; ++ i){
   Thread thread= new Thread() {    
      public void run()    {
         countdown.await(); //all threads waiting
         System.out.println("dosomething");
      }
   };
   thread.start();
}
System.out.println("Begining");
countdown.countDown();   //all threads start now!

由CountDownLatch的特性可知,其非常适合用来进行多线程分组计算,然后汇总统计结果。比如常见的一个面试题:假如有Thread1、Thread2、Thread3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?学完CountDownLatch后,你就应该有思路了(方法不局限于CountDownLatch,还有其它方法:fork/join、CyclicBarrier等)。这里结合开头说到的三个线程分别统计三个sheet页的数据,然后主线程合并统计结果为例,实现的伪代码如下:

import org.springframework.stereotype.Service;
import java.util.concurrent.*;

@Service
public class CountDownLatchService {

    public static void metric() throws ExecutionException, InterruptedException {
        ExecutorService executorService = new ThreadPoolExecutor(3, 3, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10));

        final CountDownLatch countDownLatch = new CountDownLatch(3);

        Future<Integer> submit = executorService.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                //由于我这里有return,所以我在执行method方法逻辑之前就先countDown()了,我这里还不够细
                countDownLatch.countDown();
                return method1();
            }
        });

        Future<Integer> submit1 = executorService.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                countDownLatch.countDown();
                return method2();
            }
        });

        Future<Integer> submit2 = executorService.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
//                Thread.sleep(10000);
                countDownLatch.countDown();
                return method3();
            }
        });

        countDownLatch.await();
/*
        boolean await = countDownLatch.await(2, TimeUnit.SECONDS);
        System.out.println("主线程等待2s的结果=" + await);
*/

        Integer one = 0;
        if (submit.isDone()) {
            one = submit.get();
        }

        Integer two = 0;
        if (submit1.isDone()) {
            two = submit1.get();
        }

        Integer three = 0;
        if (submit2.isDone()) {
            three = submit2.get();
        }

        System.out.println(one + two + three);

        executorService.shutdown();

    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        metric();
    }

    public static Integer method1() {
        System.out.println("我是method1");
        return 1;
    }

    public static Integer method2() {
        System.out.println("我是method2");
        return 2;
    }

    public static Integer method3() {
        System.out.println("我是method3");
        return 3;
    }

}

注意:当线程执行的某个任务耗费时间过长,其他任务由于CountDownLatch.await()方法就处于一直等待的状态,这是不合理的,这时可以调用如下方法来指定等待时长

public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); 
}

当超过timeou等待时间后,主线程便不再等待,直接合并已经计算完的结果,测试代码把上面的32、39~42行放开,38行注释掉即可验证。也就是说,使用await设定了主线程等待时间2s后,当某个任务执行的时间超过了2s,主线程便不会继续阻塞,而是"跳过"这个任务的结果。注意:我这边获取子线程的计算结果时候,使员工isDone()方法判断计算子线程任务是否执行完成,也就是说避免了直接使用FutureTask.get()方法带来的可能阻塞的问题。

从上面来看,CountDownLatch适用的场景很多,比如多线程下执行任务,然后主线程获取且合并任务结果。需要注意线程池的创建和线程阻塞(await方法)的正确使用。

CyclicBarrer和CountDownLatc区别

1、CyclicBarrer的某个线程运行到屏障点后,线程立即停止运行,进入等待状态,直到所有的线程都达到了屏障点处,所有线程才继续运行;CountDownLatch则是当线程线程运行到某个点后,进行计数减1,该线程会继续运行;

2、CyclicBarrer可重复用,CountDownLatch则不可以;

3、CyclicBarrer通过减计数方式,计数到达指定值时释放所有等待的线程,CountDownLatch通过加计数方式,计数为0时释放所有等待的线程;

4、CountDownLatch调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响,CyclicBarrer调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞。

引申阅读:

CyclicBarrier(同步屏障)的简单使用

Java中的线程池和异步任务详解

猜你喜欢

转载自blog.csdn.net/fanrenxiang/article/details/79873676