java并发-线程通讯之CountDownLatch源码分析

229 阅读2分钟

CountDownLatch描述

作用:
假如有一个主任务三个子任务,我们想让三个子任务执行完,或者执行到一定阶段再执行主任务,
那么我们就可以使用CountDownLatch来实现这个功能

大致的实现逻辑:
CountDownLatch是依赖AQS来实现的
在我看来,AQS实现lock的的一个获取锁和释放锁,都可以分成两大块,就拿获取锁来说:
1.争抢锁,也就是CAS操作state属性
2.处理线程,例如对没有抢到锁的线程调用unsafe的方法让线程陷入等待状态,并放入到队列

那么CountDownLatch的实现方式也是类似的操作,以开头说的例子为例:
1.新建一个countDownLatch,就是给内部的同步器的state设置为3
2.每当调用一次countDownLatch.countDown方法,相当于是释放锁一次,也就是给state属性 -1 操作
3.当主线程执行到 countDownLatch.await方法,那么就相当于获取锁,假如上面的3个线程,只有2个执行了
countDown方法,那么现在state的值1,AQS获取锁的原则是state0,才表示这个锁没有被别的线程持有,所以
说此时主线程检查state的值为1,不能获取到锁(打个比方,只是为了理解和lock实现的相似度),那么这个时候
会把线程park,并放入同步器的等待队列,知道state的值为0

代码dome

public static void main(String[] args) {

    CountDownLatch countDownLatch = new CountDownLatch(3);

    IntStream.range(0, 3).forEach(i -> new Thread(() ->  {
        try {
            Thread.sleep(2000);
            System.out.println("hello");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            countDownLatch.countDown();
        }
    } ) .start());

    System.out.println("启动子线程完毕");


    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("主线程启动完毕");

}

//执行结果为
启动子线程完毕
hello
hello
hello
主线程启动完毕

源码分析

构造方法

CountDownLatch countDownLatch = new CountDownLatch(3);

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    //主要就是新建了一个同步器
    this.sync = new Sync(count);
}

Sync(int count) {
    //设置了state属性为你传的值
    setState(count);
}

countDown

public void countDown() {
    //调用同步器AQS释放锁操作
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
	//主要是看下 tryReleaseShared 方法,因为这个方法是 当前同步器重写父类 AQS 的方法
    if (tryReleaseShared(arg)) {
    	//这个都是AQS公共方法了,里面主要做的事情就是唤醒AQS等待队列中的线程,当前主要是
        //针对主线程,也就是每当 调用一个 countDown方法,都会唤醒 主线程
        //然后主线程去尝试获取锁(大致意思,实际就是去检查 state的值是否为0)
        doReleaseShared();
        return true;
    }
    return false;
}

await

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

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
     // tryAcquireShared 尝试获取锁,假如state值为0,则代码继续执行,相当于获取到锁,没有阻塞
    if (tryAcquireShared(arg) < 0)
    	//假如没有获取到锁
        doAcquireSharedInterruptibly(arg);
}

//看下tryAcquireShared方法 , 很简单 就是检查下 state的值是否为0 不为0 返回-1
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}


private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //把当前线程添加到 AQS 的等待队列 , 设置 waitState 的属性为 SHARED 共享
    //因为这一路方法调过应该也发现了都是走了 AQS 提供给 共享锁的那一套逻辑,毕竟是多个线程修改一个state
    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;
                }
            }
            //获取不到的话  判断下 waitstate ,然后执行 parkAndCheckInterrupt 方法,使线程等待
            //然后然后等待 下一个子线程 调用  countDown方法,再次唤醒等待队列中的 主线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}