栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程之CountDownLatch的用法及原理笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程之CountDownLatch的用法及原理笔记

前言-CountDownLatch是什么?

CountDownLatch是具有synchronized机制的一个工具,目的是让一个或者多个线程等待,直到其他线程的一系列操作完成。

CountDownLatch初始化的时候,需要提供一个整形数字,数字代表着线程需要调用countDown()方法的次数,当计数为0时,线程才会继续执行await()方法后的其他内容。
CountDownLatch(int count);

对象中的方法
getCount:
返回当前的计数count值,
public void countDown()
调用此方法后,会减少计数count的值。
递减后如果为0,则会释放所有等待的线程
public void await()
    throws InterruptedException
调用CountDownLatch对象的await方法后。
会让当前线程阻塞,直到计数count递减至0。

如果当前线程数大于0,则当前线程在线程调度中将变得不可用,并处于休眠状态,直到发生以下两种情况之一:

1、调用countDown()方法,将计数count递减至0。

2、当前线程被其他线程打断。

public boolean await(long timeout,
     TimeUnit unit)
throws InterruptedException

同时await还提供一个带参数和返回值的方法。

如果计数count正常递减,返回0后,await方法会返回true并继续执行后续逻辑。

或是,尚未递减到0,而到达了指定的时间间隔后,方法返回false。

如果时间小于等于0,则此方法不执行等待。

实际案例 join阻塞等待线程完成

首先建立3个线程。

public class Worker1 implements Runnable {
    @Override
    public void run() {
 System.out.println("-线程1启动");
 try {
     Thread.sleep(13_000);
 } catch (InterruptedException e) {
     e.printStackTrace();
 }
 System.out.println("线程1完成--我休眠13秒rn");
    }
}

public class Worker2 implements Runnable {
    @Override
    public void run() {
 System.out.println("-线程2启动");
 try {
     Thread.sleep(3_000);
 } catch (InterruptedException e) {
     e.printStackTrace();
 }
 System.out.println("线程2完成--我休眠3秒rn");
    }
}

public class Worker3 implements Runnable {
    @Override
    public void run() {
 System.out.println("-线程3启动");
 try {
     Thread.sleep(3_000);
 } catch (InterruptedException e) {
     e.printStackTrace();
 }


 try {
     Thread.sleep(3_000);
 } catch (InterruptedException e) {
     e.printStackTrace();
 }
 System.out.println("线程3完成--我休眠6秒rn");
 System.out.println();
    }
}


public class Main {
    public static void main(String[] args) throws InterruptedException {
 Worker1 worker1 = new Worker1();
 Worker2 worker2 = new Worker2();
 Worker3 worker3 = new Worker3();

 Thread thread1 = new Thread(worker1,"线程1");
 Thread thread2 = new Thread(worker2,"线程2");
 Thread thread3 = new Thread(worker3,"线程3");

 thread1.start();
 thread2.start();
 thread3.start();

 thread1.join();
 thread2.join();
 thread3.join();
 System.out.println("主线程结束....");

    }
}

打印结果如下:

-线程3启动
-线程2启动
-线程1启动
线程2完成--我休眠3秒
线程3完成--我休眠6秒

线程1完成--我休眠13秒

主线程结束....
Process finished with exit code 0

可以看出三个线程是并行执行的。启动顺序,并不和执行完毕的顺序一致,但可以明确的是,主线程为一直阻塞,直到三个线程执行完毕。

CountDownLatch用法

阿里巴巴的数据库连接池Druid中也用了countDownLatch来保证初始化。

// 开启创建连接的线程,如果线程池createScheduler为null,
//则开启单个创建连接的线程
createAndStartCreatorThread();  

 // 开启销毁过期连接的线程
createAndStartDestroyThread(); 

自己编写一个例子:
这里模拟一种情况:
主线程 依赖 线程A初始化三个数据,才能继续加载后续逻辑。

public class CountDownArticle {
    
    public static void main(String[] args) throws InterruptedException {
 AtomicReference key = new AtomicReference<>("");
 CountDownLatch countDownLatch = new CountDownLatch(3);
     Thread t = new Thread(() -> {
     try {

  //休眠5秒,模拟数据的初始化
  TimeUnit.SECONDS.sleep(5);

  key.set("核心秘钥123456");
  System.out.println("数据1初始化完毕");

  //释放---此处可以在任何位置调用,很灵活
  countDownLatch.countDown();

  System.out.println("数据2初始化完毕");
  countDownLatch.countDown();

  System.out.println("数据3初始化完毕");
  countDownLatch.countDown();

     } catch (InterruptedException e) {
  e.printStackTrace();
     }

 });
 t.start();

 //等待数据初始化,阻塞
 countDownLatch.await();
 System.out.println("key:" + key.get());
    }
}

打印内容如下:

数据1初始化完毕
数据2初始化完毕
数据3初始化完毕
key:核心秘钥123456
CountDownLatch和Join用法的区别?

在使用join()中,多个线程只有在执行完毕之后欧才能被解除阻塞,而在CountDownLatch中,线程可以在任何时候任何位置调用countdown方法减少计数,通过这种方式,我们可以更好地控制线程的解除阻塞,而不是仅仅依赖于连接线程的完成。

join()方法的执行逻辑如下图所示:

原理

从源码可以看出,CountDownLatch是依赖于AbstractQueuedSynchronizer来实现这一系列逻辑的。

队列同步器AbstractQueuedSynchronizer
是一个用来构建锁和同步器的框架,它在内部定义了一个被标识为volatile的名为state的变量,用来表示同步状态。

多个线程之间可以通过AQS来独占式或共享式的抢占资源。

并且它通过内置的FIFO队列来完成线程的排队工作。

CountDownLatch中的Sync会优先尝试修改state的值,来获取同步状态。例如,如果某个线程成功的将state的值从0修改为1,表示成功的获取了同步状态。 这个修改的过程是通过CAS完成的,所以可以保证线程安全。

反之,如果修改state失败,则会将当前线程加入到AQS的队列中,并阻塞线程。

总结

CountDownLatch(int N) 中的计数器,可以让我们支持最多等待N个线程的操作完成,或是一个线程操作N次。

如果仅仅只需要等待线程的执行完毕,那么join可能就能满足。但是如果需要灵活的控制线程,使用CountDownLatch。

注意事项

countDownLatch.countDown();

这一句话尽量写在finally中,或是保证此行代码前的逻辑正常运行,因为在一些情况下,出现异常会导致无法减一,然后出现死锁。

CountDownLatch 是一次性使用的,当计数值在构造函数中初始化后,就不能再对其设置任何值,当 CountDownLatch 使用完毕,也不能再次被使用。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/238957.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号