CountDownLatch源码解析与应用详解

什么是countDownLatch?

CountDownLatch是一个同步工具类,它通过**一个计数器来实现的,**初始值为线程的数量。每当一个线程完成了自己的任务,计数器的值就相应得减1当计数器到达0时,表示所有的线程都已执行完毕,然后在等待的线程就可以恢复执行任务

基本使用

  • CountDownLatch(int count):count为计数器的初始值(一般需要多少个线程执行,count就设为几)。
  • countDown(): 每调用一次计数器值-1,直到count被减为0,代表所有线程全部执行完毕。
  • getCount():获取当前计数器的值。
  • await(): 等待计数器变为0,即等待所有异步线程执行完毕。
  • boolean await(long timeout, TimeUnit unit): 此方法与await()区别:
    • 此方法至多会等待指定的时间,超时后会自动唤醒,若 timeout 小于等于零,则不会等待
    • boolean 类型返回值:若计数器变为零了,则返回 true;若指定的等待时间过去了,则返回 false

例子:张三、李四和王五几个人约好去饭店一起去吃饭,这几个人都是比较绅士,要等到所有人都到齐以后才让服务员上菜。这种场景就可以用到CountDownLatch。

消费者类:

java 复制代码
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class Customers implements Runnable{
    private CountDownLatch latch;
    private String name;

    public Customers(CountDownLatch latch, String name) {
        this.latch = latch;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
            Random random = new Random();
            System.out.println(sdf.format(new Date()) + " " + name + "出发去饭店");
            Thread.sleep((long) (random.nextDouble() * 3000) + 1000);
            System.out.println(sdf.format(new Date()) + " " + name + "到了饭店");
            latch.countDown(); //到了饭店就通知一声,让count减1
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

服务员类:

java 复制代码
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;

//    服务员类
public class Waitress implements Runnable{
    private CountDownLatch latch;
    private String name;

    public Waitress(CountDownLatch latch, String name) {
        this.latch = latch;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
            System.out.println(sdf.format(new Date()) + " " + name  + "等待顾客");
            latch.await(); //让服务员等待,所有的人员到齐在上菜
            System.out.println(sdf.format(new Date()) + " " + name  + "开始上菜");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

测试类:

java 复制代码
package 循环阑珊;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchTester {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latchs=new CountDownLatch(3);

        List<Thread> threads=new ArrayList<>(3);
        threads.add(new Thread(new Customers(latchs,"张三")));
        threads.add(new Thread(new Customers(latchs,"李四")));
        threads.add(new Thread(new Customers(latchs,"王五")));
        for (Thread thread : threads) {
            thread.start();
        }

        try {
            Thread.sleep(100);
            new Thread(new Waitress(latchs, "小美")).start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //等待所有线程执行完
        for (Thread thread : threads) {
            thread.join();
        }
    }
}

运行结果:

13:49:32.503 李四出发去饭店 13:49:32.503 王五出发去饭店 13:49:32.503 张三出发去饭店 13:49:32.575 小美等待顾客 13:49:34.319 李四到了饭店 13:49:35.050 张三到了饭店 13:49:35.836 王五到了饭店 13:49:35.836 小美开始上菜

CountDownLatch基本原理

CountDownLatch有一个内部类叫做Sync ,它继承了AbstractQueuedSynchronizer类,其中维护了一个整数state ,并且保证了修改state的可见性和原子性。

创建CountDownLatch实例时,也会创建一个Sync的实例 ,同时把计数器的值传给Sync实例,具体是这样的

java 复制代码
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    //创建一个Sync的实例对象,并且将count传入进去进行维护
    this.sync = new Sync(count);
}

countDown方法

countDown方法中,只调用了Sync实例的releaseShared方法,具体是这样的:

java 复制代码
public void countDown() {
    sync.releaseShared(1);
}

releaseShared()方法,先对计数器进行减1操作,如果减1后的计数器为0,唤醒被awit方法阻塞的所有线程:

java 复制代码
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) { //对计数器进行减一操作
        doReleaseShared();//如果计数器为0,唤醒被await方法阻塞的所有线程
        return true;
    }
    return false;
}

在的tryReleaseShared方法,先获取当前计数器的值,如果计数器为0时,就直接返回;如果不为0时,使用CAS方法对计数器进行减1操作,操作成功看计数器是不是等于0,具体是这样的:

java 复制代码
protected boolean tryReleaseShared(int releases) {
    for (;;) {//死循环,如果CAS操作失败就会不断继续尝试。
        int c = getState();//获取当前计数器的值。
        if (c == 0)// 计数器为0时,就直接返回。
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))// 使用CAS方法对计数器进行减1操作
            return nextc == 0;//如果操作成功,返回计数器是否为0
    }
}

await方法

await方法中,只调用了Sync实例的acquireSharedInterruptibly方法,具体是这样的:

java 复制代码
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

其中acquireSharedInterruptibly方法,判断计数器是否为0,如果不为0则阻塞当前线程:

java 复制代码
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted()) //线程中断就报中断异常
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)//判断计数器是否为0
        doAcquireSharedInterruptibly(arg);//如果不为0则阻塞当前线程
}

其中tryAcquireShared方法,是AbstractQueuedSynchronizer中的一个模板方法 ,其具体实现在Sync类 中,其主要是判断计数器是否为零,如果为零则返回1,如果不为零则返回-1

java 复制代码
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

CountDownLatch应用场景

  1. 某个线程需要在其他n个线程执行完毕后再向下执行
  2. 多个线程并行执行同一个任务,提高响应速度

:如果多个线程并行执行一个任务,则初始count值必须与启动线程数保持一致,即count为10,则必须开启10 个异步线程

相关推荐
2401_8574396910 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66611 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索13 分钟前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨18 分钟前
Filter和Listener
java·filter
qq_49244844623 分钟前
Java实现App自动化(Appium Demo)
java
阿华的代码王国31 分钟前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
德育处主任Pro1 小时前
『Django』APIView基于类的用法
后端·python·django
找了一圈尾巴1 小时前
前后端交互通用排序策略
java·交互
哎呦没3 小时前
SpringBoot框架下的资产管理自动化
java·spring boot·后端
2401_857600953 小时前
SpringBoot框架的企业资产管理自动化
spring boot·后端·自动化