CountDownLatch详解

CountDownLatch是一种协调工具类,用于控制线程的执行顺序。通过AQS实现,允许一个或多个线程等待其他线程完成操作。核心方法包括countDown和await,前者减少计数,后者在计数达到零前阻塞线程。
摘要由CSDN通过智能技术生成

简介

通过这个类可以控制线程的执行顺序,比如有三个线程A、B、C,我需要在A、B两个线程执行完某个步骤之后再执行线程C的某些步骤,则可以通过CountDownLatch这个类进行控制,实现原理也是通过AQS来实现的。大家可以看看CountDownLatch这个类的源码,在源码的注释中给了一个很好的例子,后面我也会给出我本地测试的一个例子。


实现原理

  1. 核心的内部类Sync
  • 继承了AbstractQueuedSynchronizer这个类。
    (1)重写了tryAcquireShared和tryReleaseShared,非常简单的代码,但是就是这么简单的代码就实现了一个非常好的功能。
    // state等于0,返回1,表示可以获取锁,1这个值在后面还会用到
    protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
    	
    	// 释放锁,每次释放锁就是直接将state的值减一
        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;
            }
        }
    
  1. 三个核心方法
    • countDown()
    // 看到这就知道了countDown操作相当于于释放共享锁
    public void countDown() {
        sync.releaseShared(1);
    }
    
    public final boolean releaseShared(int arg) {
    	// 这里进行state减一操作,如果减一后的结果是0,则返回true,表示锁没有被任何线程
    	//占用,则就可以进行doReleaseShared操作。	     
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    

    相当于countDown每调用一次,state的值就减一,直到state等于zero时,才开始释放共享锁。

  • await()

没有获取到锁,除非被中断,否则一直等着

public void await() throws InterruptedException {
        // 获取锁,如果state大于0,这里调用会阻塞的
        sync.acquireSharedInterruptibly(1);
    }

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 获取不到锁,返回-1
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    // 如果获取到锁后,会进行传播操作,主要是后面可能有线程共享锁
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 这里如果获取不到锁,就会调用LockSupport的park方法阻塞当前线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • await(long timeout, TimeUnit unit)

在规定的时间内还没有获取到锁,也直接返回

public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
    }

private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return true;
                    }
                }
                nanosTimeout = deadline - System.nanoTime();
                // 如果超时了,直接返回false。
                if (nanosTimeout <= 0L)
                    return false;
                // 当前线程等待一段时间
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

从上面可以看到CountDownLatch这个类非常简单,核心的代码已经在AbstractQueuedSynchronizer这个类中写好了,所以AQS才是关键。


DEMO

import java.util.concurrent.CountDownLatch;

/**
 * @Author: jiangcw
 * @Date: 2019-9-22 下午 12:52
 * @Version 1.0
 */
public class Test {

    public static void main(String[] args) throws Exception {
        CountDownLatch latch = new CountDownLatch(2);
        new Thread(() -> {
            System.out.println("我是老大,我先准备一下,准备好了我通知你们");
            latch.countDown();
            System.out.println("我是老大,多谢等我呀");
        }).start();

        new Thread(() -> {
            System.out.println("我是老二,我先准备一下,准备好了我通知你们");
            latch.countDown();
            System.out.println("我是老二,多谢等我呀");
        }).start();

        new Thread(() -> {
            System.out.println("我是老三,老大和老二你们准备好了记得通知我呀,然后我再问候你们");
            try {
                latch.await();
                System.out.println("我是老三,我收到你们的通知了,我问候你呵呵呵");
            }catch (InterruptedException e) {}
        }).start();

    }
}

如上所示,三个兄弟,老三需要等老大和老二准备好了之后,才能问候他两,所以等老大和老二都准备好后,调用countDown方法进行state减一操作,相当于一次通知,等state等于0后,表示老三收到两个兄弟的通知了,就可以问候他两了。


总结:
这个类的步骤如下:
(1)当调用new CountDownLatch(N)实例化时,会将state设置为N,表示当前锁已经被占有;
(2)当调用countDown时,如果state的值大于0,则将state进行减一操作,直到state的值等于0,当其值等于0后,等待的线程就会收到通知,实现获取锁操作。
(3)调用await方法时,如果state的值大于0,则会调用LockSupport的park之类的方法阻塞当前线程,直接state等于0后,其他线程主动调用LockSupport.unpark(Thread)方法,使线程从阻塞中恢复。
(4)对于有多个线程都调用await方法阻塞,是通过共享锁机制,调用setHeadAndPropagate这个方法让后继的线程从阻塞中恢复的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值