CountDownLatch源码分析

CountDownLatch简介

简介:CountDownLatch是基于AQS共享锁的一个锁工具,用来线程同步问题,针对的是一个线程必须等待多个线程完成后才运行的场景,count就是代表线程的个数。

CountDownLatch基于AQS的共享锁,如果你对这块知识还不了解,可以看我之前写的文章:AQS源码详细分析,让你掌握AQS原理,独占锁、共享锁、Conditio

CountDownLatch举例

package juc;

import java.util.concurrent.CountDownLatch;

public class TestCountDownLatch {

    public static void main(String[] args) {
        final CountDownLatch countDownLatch = new CountDownLatch(2);

        new Thread(){
            @Override
            public void run() {

                System.out.println("线程1准备");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1结束");
                countDownLatch.countDown();
               
            }
        }.start();

        new Thread(){
            @Override
            public void run() {

                System.out.println("线程2准备");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程2结束");
                countDownLatch.countDown();
            }
        }.start();

        new Thread(){
            @Override
            public void run() {

                try {
                    System.out.println("开始等待线程1、线程2");
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("线程1、线程2准备完毕");
            }
        }.start();


    }
}

在这里插入图片描述

CountDownLatch源码分析

CountDownLatch结构
首先看下CountDownLatch的结构
在这里插入图片描述
从上图可以看出,CountDownLatch的构造方法只有一个CountDownLatch(int count)就是设置共享锁同时可以容纳的线程数。

CountDownLatch内部实现了一个Sync,Sync继承于AQS,并且是共享锁模式。

1、初始化

public CountDownLatch(int count) {
    //如果小于0,抛出参数异常
    if (count < 0) throw new IllegalArgumentException("count < 0");
    //初始化共享锁AQS
    this.sync = new Sync(count);
}

//将Sync从AQS继承的count,设置为count、
Sync(int count) {
    setState(count);
}

2、await()

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

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    //如果被中断,抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
     //申请共享锁,申请成功就返回
    if (tryAcquireShared(arg) < 0)
        //如果申请失败,就尝试入队挂起
        doAcquireSharedInterruptibly(arg);
}

protected int tryAcquireShared(int acquires) {
    //等于0返回1,不等于0返回-1
    return (getState() == 0) ? 1 : -1;
}

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //之前AQS时说过,将thread包装为Node入队,不再详细叙述
    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;
                }
            }
            //如果没抢到锁,就尝试着挂起,抢锁过程中如果被中断,则抛出异常
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

await()十分简单,如果state==0,就返回,如果不等于0,就挂起,
相当于共享锁中的抢锁过程。

3、countDown()

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

public final boolean releaseShared(int arg) {
	//释放锁
    if (tryReleaseShared(arg)) {//当所有线程都调用countDown()后,state等于0,唤醒等待的线程,
        doReleaseShared();
        return true;
    }
    return false;
}

protected boolean tryReleaseShared(int releases) {
 
    // 利用CAS自旋,知道CAS将state-1,如果state为0,返回true,不为0,返回false。
    //当所有线程都调用countDown()后,state等于0。
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}


private void doReleaseShared() {

    for (;;) {
        Node h = head;
        //如果队列不空,就将队列中的第二节点唤醒
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                //唤醒
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                、
        }
        if (h == head)                   
            break;
    }
}

unparkSuccessor(h)

unparkSuccessor(h)中会调用

LockSupport.unpark(s.thread);

唤醒线程,会回到之前await()阻塞的地方。

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //之前AQS时说过,将thread包装为Node入队,不再详细叙述
    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;
                }
            }
            //如果没抢到锁,就尝试着挂起,抢锁过程中如果被中断,则抛出异常
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //这里这里这里这里
                //线程被唤醒,停止阻塞如果线程被中断的话,就抛出异常
                //如果线程不被中断,就自旋去请求锁,请求锁成功然后返回
                //await调用完成,返回
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

总结

从上述代码可以看出,一个线程调用countDown()会将state-1。

await的作用就是当state!=0的时候,线程会阻塞,也就是当count个线程成功调用countDown()后,state会等于0,await()会停止阻塞。

所以说CountDownLatch可以适用于一个线程等待若干个线程的场景。

TIP

1、当线程调用countDown()后,不会阻塞,会继续执行代码。
2、CountDownLatch只能使用一次,不可重复利用。

如果你了解了AQS的共享锁的原理,理解CountDownLatch毫无压力。
AQS里面还是有很多经典的思想的,感兴趣的同学可以看我之前写的AQS的文章:AQS源码详细分析,让你掌握AQS原理,独占锁、共享锁、Conditio

posted @ 2021-08-22 10:09  张孟浩Jay  阅读(42)  评论(0编辑  收藏  举报