java多线程之CountDownLatch源码解析

前言

本篇开始分析CountDownLatch的源码,分析结束后,会用一个示例展示CountDownLatch的应用场景。

1、简介

CountDownLatch本质上是一个共享锁,内部维护了一个Sync,Sync是继承AQS抽象类的,它没有公平和不公平之分,含义是允许一个或多个线程等待其它线程的操作执行完毕后再执行后续的操作。

2、分析源码

2.1、函数列表

//构造一个内部维护给定计数器的CountDownLatch
CountDownLatch(int count)
// 等待,直到CountDownLatch内部计数器为0
void await()
// 在指定时间内等待CountDownLatch内部计数器为0
boolean await(long timeout, TimeUnit unit)
// CountDownLatch的内部计数器减1
void countDown()
// 返回当前计数。
long getCount()
String toString()

2.2、分析await方法

这个是CountDownLatch中的await方法

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

acquireSharedInterruptibly方法的实现在AQS中,源码如下:

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //尝试获取共享锁,成功返回
    if (tryAcquireShared(arg) < 0)
    	//如果失败则进入AQS的队列中排队等待被唤醒
        doAcquireSharedInterruptibly(arg);
}

tryAcquireShared在内部类Sync中重写了,源码如下:

protected int tryAcquireShared(int acquires) {
	//state(内部维护的计数器)为0,则获取共享锁成功,否则就排队
	return (getState() == 0) ? 1 : -1;
}

doAcquireSharedInterruptibly方法是AQS共享锁排队重试的方法,之前ReentrantReadWriteLock和Semaphore类都有讲过,不再重复。

2.3、分析countDown方法

这个是CountDownLatch中的countDown方法

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

releaseShared方法在AQS中,源码如下:

public final boolean releaseShared(int arg) {
	// 尝试释放共享锁,如果成功了,就唤醒排队的线程
	if (tryReleaseShared(arg)) {
       doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared的方法在CountDownLatch的内部类Sync中,源码如下:

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
     for (;;) {
     	 //获取计数器值
         int c = getState();
         //为0就返回
         if (c == 0)
             return false;
         //减1
         int nextc = c-1;
         //CAS更新值,计数器值由1变为0的时候才返回true
         if (compareAndSetState(c, nextc))
             return nextc == 0;
     }
 }

doReleaseShared方法在AQS中实现,作用是依次唤醒CLH队列中排队的线程,此方法之前也分析过,不再重复。

3、示例

下面的代码的业务逻辑,比如下单业务:

  1. 主线程就绪
  2. 3个线程开始处理业务
  3. 业务逻辑处理完,主线程继续
public class CountDownLatchTest {

    public static final CountDownLatch WORK_THREAD = new CountDownLatch(3);
    public static final CountDownLatch MAIN_THREAD = new CountDownLatch(1);

    public static void main(String[] args) throws Exception{
        for (int i=0; i<3; i++){
            new Thread(() -> { method(); }).start();
        }
        //主线程逻辑
        Thread.sleep(2000);
        System.out.println("主线程准备就绪……");
        MAIN_THREAD.countDown();
        WORK_THREAD.await();
        System.out.println("业务处理完,主线程继续……");
    }

    public static void method(){
        System.out.println("waiting业务的线程名称是:" + Thread.currentThread().getName());
        try {
            //处理业务
            MAIN_THREAD.await();
            Thread.sleep(100);
            System.out.println("finish业务的线程名称是:" + Thread.currentThread().getName());
            WORK_THREAD.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

waiting业务的线程名称是:Thread-2
waiting业务的线程名称是:Thread-1
waiting业务的线程名称是:Thread-0
主线程准备就绪……
finish业务的线程名称是:Thread-1
finish业务的线程名称是:Thread-2
finish业务的线程名称是:Thread-0
业务处理完,主线程继续……

结束语

上面示例中,主线程维护的CountDownLatch的wait()调用了3次,countDown()调用了1次,countDown()方法将state减到0后,之前排队的3个wait()方法阻塞的线程都被唤醒继续执行。

写到这里,看完我前面分析ReentrantReadWriteLock和Semaphore的同学应该更加理解了CountDownLatch中为什么使用共享锁而不是互斥锁。

共享锁的释放是一个接一个的通知队列中排队的线程,而互斥锁的释放是一次只通知一个

如果我的分析对你有帮助,请点个赞再走,谢谢大家!

原创文章 55 获赞 76 访问量 17万+

猜你喜欢

转载自blog.csdn.net/cool_summer_moon/article/details/106100770