JUC源码系列之CountDownLatch源码解析

目录

  • CountDownLatch 简介
  • CountDownLatch 使用示例
  • CountDownLatch 优缺点
  • CountDownLatch 源码解析
  • 扩展

CountDownLatch 简介

CountDownLatch 是一个同步工具类,用来协调多个线程之间的同步,达到线程间通信的效果。CountDownLatch 允许一个或多个线程一直等待,直到其他线程运行完成后再执行。

使用示例

  • 让多个线程等待
public class Test {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    System.out.println("运动员["+Thread.currentThread().getName()+"]准备完毕,等待号令...");
                    countDownLatch.await();
                    System.out.println("运动员["+Thread.currentThread().getName()+"]开跑...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        System.out.println("裁判准备发令...");
        Thread.sleep(2000);
        countDownLatch.countDown();
        System.out.println("执行发令...");
        countDownLatch.countDown();
    }
}

输出结果:

裁判准备发令...
运动员[Thread-0]准备完毕,等待号令...
运动员[Thread-1]准备完毕,等待号令...
运动员[Thread-2]准备完毕,等待号令...
运动员[Thread-3]准备完毕,等待号令...
运动员[Thread-4]准备完毕,等待号令...
执行发令...
运动员[Thread-0]开跑...
运动员[Thread-2]开跑...
运动员[Thread-4]开跑...
运动员[Thread-3]开跑...
运动员[Thread-1]开跑...
  • 让一个线程等待
public class Test {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                System.out.println("运动员["+Thread.currentThread().getName()+"]到达终点...");
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        System.out.println("工作人员开始统计排名...");
    }
}

输出结果:

运动员[Thread-0]到达终点...
运动员[Thread-1]到达终点...
运动员[Thread-2]到达终点...
运动员[Thread-3]到达终点...
运动员[Thread-4]到达终点...
工作人员开始统计排名...

优缺点

CountDownLatch 是一次性的,计算器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当 CountDownLatch 使用完毕后,它不能再次被使用。

源码解析

CountDownLatch 是基于 AQS 同步器的共享锁实现的内部逻辑,通过代码可以看到,CountDownLatch 类主要的几个方法都是通过 Sync 类实现的,而 Sync 类就是 AQS 的实现类。

public class CountDownLatch {
    // AQS 同步器的实现类
    private final Sync sync;
    // 构造方法,初始化计数器
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
    // 阻塞加共享锁,可响应中断
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    // 阻塞加共享锁,可响应中断,并设置超时时间
    public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    // 计数器减 1,调用释放共享锁方法
    public void countDown() {
        sync.releaseShared(1);
    }
}

Sync 继承了 AQS 并实现了共享锁相关的两个方法 tryAcquireShared 及 tryReleaseShared

private static final class Sync extends AbstractQueuedSynchronizer {
    // 初始化同步状态值
    Sync(int count) {
        setState(count);
    }
    // 加共享锁
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }
    // 释放共享锁
    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

大体结构基本清楚了,现在开始理流程。首先需要创建 CountDownLatch 对象,并设置一个初始计数器值,最终是调用了 AQS 的 setState 方法将该值设置到了 AQS 的 state 同步状态值上。

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

Sync(int count) {
    setState(count);
}

然后主线程会执行 await() 方法进行等待,这个方法最终调用了 AQS 的 acquireSharedInterruptibly 方法执行加共享锁,acquireSharedInterruptibly 方法首先会调用 tryAcquireShared 方法尝试加锁,如果尝试失败,则进入等待队列并挂起线程。因为上一步初始化了 state,很明显这里 state != 0,所以加锁失败,线程进入等待队列。

// 阻塞加共享锁,可响应中断
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
// 加共享锁
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
// 加共享锁
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

接着子线程会执行 countDown() 方法,这个方法调用 AQS 的 releaseShared 方法释放共享锁,其实就是让 AQS 的同步状态减 1,当同步状态值减到 0 时,需要唤醒上一步在等待队列中挂起的线程,此时这些线程就可以加锁成功,从而执行后续的操作。

// 计数器减 1,调用释放共享锁方法
public void countDown() {
    sync.releaseShared(1);
}
// 释放共享锁
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) { // 当同步状态减至 0 时,唤醒等待队列中的线程
        doReleaseShared();
        return true;
    }
    return false;
}
// 释放共享锁
protected boolean tryReleaseShared(int releases) {
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc)) // 通过 cas 让同步状态减 1
            return nextc == 0; // 如果减 1 后的状态为 0 返回 true
    }
}

扩展

CountDownLatch 与 Thread.join

CountDownLatch 的作用就是允许一个或多个线程等待其他线程完成操作,看起来有点类似 join() 方法。CountDownLatch 可以手动控制在 n 个线程里调用 n 次 countDown() 方法使计数器进行减一操作,也可以在一个线程里调用 n 次执行减一操作。而 join() 的实现原理是不停检查 join 线程是否存活,如果 join 线程存活则让当前线程永远等待。所以 CountDownLatch 使用起来较为灵活。

CountDownLatch 与 CyclicBarrier

两者都能够实现线程之间的等待,只不过它们侧重点不同:

  • CountDownLatch 一般用于一个或多个线程,等待其他线程执行完任务后,再才执行。
  • CyclicBarrier 一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行。
  • CountDownLatch是减计数,计数减为0后不能重用;而CyclicBarrier是加计数,可置0后复用。

CyclicBarrier 的源码分析请看下篇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值