CountDownLatch

目录

 

CountDownLatch

使用案例

使用场景

源码分析


CountDownLatch

Countdownlatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。从命名可以解读到countdown是倒数的意思,类似于我们倒计时的概念。

Countdownlatch提供了两个方法,一个是countDown,一个是await, CountDownLatch初始化的时候需要传入一个整数,在这个整数倒数到0之前,调用了await方法的程序都必须要等待,然后通过countDown来倒数。


使用案例

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

    new Thread(()->{
        countDownLatch.countDown();
    },"t1").start();

    new Thread(()->{
        countDownLatch.countDown();
    },"t2").start();

    new Thread(()->{
        countDownLatch.countDown();
    },"t3").start();

    countDownLatch.await();
    System.out.println("所有线程执行完毕");
}

从代码的实现来看,有点类似join的功能,但是比join更加灵活。CountDownLatch构造函数会接收一个int类型的参数作为计数器的初始值,当调用CountDownLatch的countDown方法时,这个计数器就会减一。


使用场景

可以通过Countdownlatch实现最大的并行请求,也就是可以让N个线程同时执行。

也可以控制线程间的执行顺序,比如应用程序启动之前,需要确保相应的服务已经启动。


源码分析

CountDownLatch类存在一个内部类Sync,Sync继承了AQS

CountDownLatch - > await

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

await函数会使得当前线程在countdownlatch倒计时到0之前一直等待,除非线程别中断。从源码中可以得知await方法会转发到Sync的acquireSharedInterruptibly方法。

AQS - > acquireSharedInterruptibly

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //判断线程是否中断
        if (Thread.interrupted())
            throw new InterruptedException();
        //如果当前线程状态等于0则返回1,否则返回-1,返回-1表示需要阻塞
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }


    // 返回当前线程状态
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }


    //通过下面代码可以看出count值既是state,如果计数器为0,表示不需要阻塞
    //否则,只有在满足条件的情况下才会被唤醒
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

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

    protected final void setState(int newState) {
        state = newState;
    }

AQS - > doAcquireSharedInterruptibly

获取共享锁。

    /**
     * Acquires in shared interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED); //创建一个共享模式的节点添加到队列中
        boolean failed = true;
        try {
            for (;;) { //自旋等待共享锁释放,也就是等待计数器等于0
                final Node p = node.predecessor(); //获得当前节点的前一个节点
                if (p == head) {
                    int r = tryAcquireShared(arg); //判断尝试获取锁 返回1或-1
                    if (r >= 0) { //r>=0表示计数器已经归零了,则释放当前的共享锁
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //当前节点不是头节点,则尝试让当前线程阻塞
                //第一个方法是判断是否需要阻塞,第二个方法是阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

AQS - > setHeadAndPropagate

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // 记录头节点
        setHead(node); //设置当前节点为头节点
        //前面传过来的propagate是1,所以会进入下面的代码
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            //获得当前节点的下一个节点
            //如果下一个节点是空表示当前节点为最后一个节点,或者下一个节点是share节点
            Node s = node.next; 
            if (s == null || s.isShared())
                doReleaseShared(); //唤醒下一个共享节点
        }
    }

doReleaseShared

释放共享锁,通知后面的节点。

    private void doReleaseShared() {
        for (;;) {
            Node h = head; //获得头节点
            if (h != null && h != tail) { //如果头节点不为空且不等于tail节点
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) { //头节点状态为SIGNAL
                    //修改当前头节点的状态为0,避免下次再进入到这个里面
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h); //释放后续节点
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

countDown

以共享模式释放锁,并且会调用tryReleaseShared函数,根据判断条件也可能会调用doReleaseShared函数。

    public void countDown() {
        sync.releaseShared(1);
    }

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) { //如果为true,表示计数器已归0了
            doReleaseShared(); //唤醒处于阻塞的线程
            return true;
        }
        return false;
    }

AQS - > tryReleaseShared

这里主要是对state做原子递减,其实就是我们构造的CountDownLatch的计数器,如果等于0返回true,否则返回false

    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
   }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@从入门到入土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值