目 录CONTENT

文章目录

CountDownLatch - 同步计数器

FatFish1
2024-10-23 / 0 评论 / 0 点赞 / 49 阅读 / 0 字 / 正在检测是否收录...

CountDownLatch的概念

CountDownLatch的作用很简单,就是一个或者一组线程在开始执行操作之前,必须要等到其他线程执行完才可以。例如等待学生考完收卷的老师。

源码分析

核心成员变量

private final Sync sync;

是一个基于AQS实现的同步器

Sync对AQS的重写

Sync(int count) {
    setState(count);
}
int getCount() {
    return getState();
}

其中同步器的state承接CountDownLatch的计数count

tryAcquireShared

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

可见计数count是0,就返回1

tryReleaseShared

if (c == 0)
    return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
    return nextc == 0;

可见是在做倒数操作

构造函数

this.sync = new Sync(count);

构造同步器sync

await()await(timeout, TimeUnit) - 等待方法

sync.acquireSharedInterruptibly(1);

await方法调用的就是同步器的acquire方法

if (Thread.interrupted() ||
    (tryAcquireShared(arg) < 0 &&
     acquire(null, arg, true, true, false, 0L) < 0))

回顾下,就是调用tryAcquireShared,上面看到了具体实现,那么小于0的条件是:计数器count != 0,业务含义就是还有任务没有处理完,要等待

这个时候,执行await的线程(一般是主线程)就会进同步队列里面等着去

除非使用带时间的await方法,到期会重新自旋获取锁

countDown() - 计数方法

sync.releaseShared(1);

countdown方法调用的就是释放锁方法,每次使计数减1

使用的时候,默认每个分配任务的子线程持有一把锁,子线程调用countDown后,归还一把,指导计数count变成0,使用await阻塞的线程就可以拿到锁走下去

countDownLatch使用案例

CountDownLatch典型用法:某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒

一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactoryBuilder().setNameFormat("countDownLatchTest-%s").build());
        CountDownLatch countDownLatch = new CountDownLatch(5);
        int i = 5;
        while (i --> 0) {
            executorService.submit(new ext(countDownLatch));
        }
        countDownLatch.await();
        System.out.println("all is over");
    }

    static class ext implements Runnable {
        private CountDownLatch countDownLatch;

        public ext(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(RandomUtils.getInt(10));
                System.out.printf("%s thread is over%n", Thread.currentThread().getName());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            countDownLatch.countDown();
        }
    }
}

CountDownLatch的问题

  • CountDownLatch是一次性的 , 计数器的值只能在构造方法中初始化一次 , 之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后 , 它不能再次被使用。因此这个方法比较适合用于程序启动时一次性加载配置。

  • CountDown的子线程如果抛异常很可能走不到countDown环节,await的线程就有可能永远被阻塞,存在风险这里注意使用try-finally逻辑让countDown()方法总能被调用

0

评论区