概要
有时候会遇到一些需要使用多线程的业务场景,比如我现在要获取用户的信息,这些信息存在于不同的中心,需要调用回来组成完整的用户信息,如果采用同步的话,可能会很慢,所以采用异步的方式,最后等所有的子线程调用完成后,主线程才返回,这里我们采用CountDownLatch
提供的方法实现线程同步。
CountDownLatch的原理
CountDownLatch(倒计时门闩)是一种多线程同步工具,用于在一个或多个线程等待其他线程完成任务后再继续执行。
在创建 CountDownLatch 对象时,需要指定一个初始计数值(count)。
当一个线程完成了自己的任务后,可以调用 CountDownLatch 的 countDown() 方法,将 count 值减1。
其他线程可以通过调用 CountDownLatch 的 await() 方法来等待 count 值变为0。
当 count 值变为0 时,所有等待的线程会被唤醒,可以继续执行后续的操作。
简而言之,CountDownLatch 是通过一个计数器来实现线程间的等待和通知机制。线程在等待某些条件满足时会阻塞,直到计数器的值变为0,才能继续执行。
常见的应用场景是,将一个任务分解为多个子任务,并让多个线程并行执行这些子任务。主线程等待所有子线程完成任务后,再进行后续的处理。
需要注意的是,CountDownLatch 的计数值只能在初始化时设置一次,并且无法重置。一旦计数值变为0,就无法再次使用 CountDownLatch 进行等待。
注意事项
- 当count变为0时,即使调用了await()方法,线程也不会等待,CountDownLatch内部用CAS(乐观锁)的方式进行自旋;
- 如果count没有减为0,那么会一直自旋,导致线程
死锁
。
代码演示
新建一个Worker类,将CountDownLatch 作为成员变量传入,同时在执行结束后,在finally中countDown,防止子线程异常结束后计数器没有减。
class Worker extends Thread {
private CountDownLatch latch;
/**
* 等待时间
*/
private long time;
/**
* 通过构造方法传入latch
* @param latch
*/
public Worker(CountDownLatch latch, long time) {
this.latch = latch;
this.time = time;
}
@Override
public void run() {
try {
System.out.println("【子线程】" + Thread.currentThread().getName() + ", 开始执行......");
Thread.sleep(time);
System.out.println("【子线程】" + Thread.currentThread().getName() + ", 执行结束!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//计数器减一 (防止线程崩掉,在finally中countDown)
latch.countDown();
}
}
}
主线程中新建三个子线程,将latch传入,await()方法可以传入超时时间,如果在规定时间内没有返回,则计数器归零,主线程继续执行
public static void main(String[] args) throws InterruptedException {
//count初始量为3
CountDownLatch latch = new CountDownLatch(3);
System.out.println("【主线程】" + Thread.currentThread().getName() + ", 开始执行!");
new Worker(latch, 2000).start();
new Worker(latch, 1500).start();
new Worker(latch, 5000).start();
// boolean await = latch.await(4, TimeUnit.SECONDS);//等待4秒自动释放,防止死锁,返回false代表子线程超时,返回true代表正常结束
// System.out.println("【主线程】" + Thread.currentThread().getName() + ", 全部结束!线程是否超时:" + await);
latch.await(); //主线程等待子线程全部结束
System.out.println("【主线程】" + Thread.currentThread().getName() + ", 全部结束!");
}
结果
设置超时时间后的执行结果(未超时返回true, 超时返回false)
【主线程】main, 开始执行!
【子线程】Thread-0, 开始执行…
【子线程】Thread-1, 开始执行…
【子线程】Thread-2, 开始执行…
【子线程】Thread-1, 执行结束!
【子线程】Thread-0, 执行结束!
【主线程】main, 全部结束!线程是否超时:false//代表子线程超时了
未设置超时时间,会等待子线程全部结束
【主线程】main, 开始执行!
【子线程】Thread-0, 开始执行…
【子线程】Thread-1, 开始执行…
【子线程】Thread-2, 开始执行…
【子线程】Thread-1, 执行结束!
【子线程】Thread-0, 执行结束!
【子线程】Thread-2, 执行结束!
【主线程】main, 全部结束!