为了账号安全,请及时绑定邮箱和手机立即绑定

JUC之CountDownLatch的源码和使用场景分析

标签:
Premiere

开始讲解之前,自定义ThreadPoolExecutor和Task。

public class CustomThreadPoolExecutor extends ThreadPoolExecutor {    public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }    public static class CustomTask<V> extends FutureTask<V> {        public CustomTask(Callable<V> callable) {            super(callable);
        }        public CustomTask(Runnable runnable, V result) {            super(runnable, result);
        }

    }

}
  • 为什么要需要线程池 ?
    线程资源必须通过线程池提供,不允许在应用中自行显式的创建线程。使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池的话,有可能造成系统创建大量的同类线程而消耗完线程而导致消耗完内存或者过度切换的问题。

  • 为什么要不能通过Executors去创建线程池?
    因为FixedThreadPool和SingleThreadPool使用的是无界队列,会堆积大量的请求,造成OOM。还有CachedThreadPool和ScheduledThreadPool会造成Integer.MAX_VALUE,会创建大量的线程,造成OOM


分析CountDownLatch

CountDownLatch用于协调多个线程的同步,能让一个线程在等待其他线程执行完任务后,再继续执行。内部是通过一个计数器去完成实现。

静态内部类Sync继承AQS,通过state变量完成计数器的实现。

    /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }        int getCount() {            return getState();
        }        protected int tryAcquireShared(int acquires) {            return (getState() == 0) ? 1 : -1;
        }        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;
            }
        }
    }

CountDownLatch构造方法,count代表需要执行的线程数量。

    /**
     * Constructs a {@code CountDownLatch} initialized with the given count.
     *
     * @param count the number of times {@link #countDown} must be invoked
     *        before threads can pass through {@link #await}
     * @throws IllegalArgumentException if {@code count} is negative
     */
    public CountDownLatch(int count) {        if (count < 0) throw new IllegalArgumentException("count < 0");        this.sync = new Sync(count);
    }

在计数值 > 0的情况下,每当一个线程完成任务,计数减去1。

    /**
     * Decrements the count of the latch, releasing all waiting threads if
     * the count reaches zero.
     *
     * <p>If the current count is greater than zero then it is decremented.
     * If the new count is zero then all waiting threads are re-enabled for
     * thread scheduling purposes.
     *
     * <p>If the current count equals zero then nothing happens.
     */
    public void countDown() {
        sync.releaseShared(1);
    }

让当前线程等待其他线程,直到计数为0,除非当前线程被中断了。

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

让当前线程等待其他线程,如果超过指定的timeout时间范围,那么忽略要等待的线程,
直接执行。

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

返回当前计数量。

    /**
     * Returns the current count.
     *
     * <p>This method is typically used for debugging and testing purposes.
     *
     * @return the current count
     */
    public long getCount() {        return sync.getCount();
    }

当计数器应该为0,所有的线程执行完自己的任务。在CountDownLatch等待的线程,可以继续执行的任务。


使用场景

适用于一个任务的执行需要等待其他任务执行完毕,方可执行的场景。

场景1:一群学生在教室考试,学生们都完成了作答,老师才可以进行收卷操作。

场景2:110跨栏比赛中,所有运动员准备好起跑姿势,进入到预备状态,等待裁判一声枪响。裁判开了枪,所有运动员才可以开跑。

CountDownLatch是一次性的,只能通过构造方法设置初始计数量,计数完了无法进行复位,不能达到复用。而CyclicBarrier可以实现复用。


场景1

一个线程的执行要等待其他线程执行完毕后,才能继续执行。

    public static void test1() {        final CountDownLatch countDownLatch = new CountDownLatch(2);

        ExecutorService executorService = new CustomThreadPoolExecutor(2,                2, 0L,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));        for (int i = 0; i < 2; i++) {
            CustomThreadPoolExecutor.CustomTask task = new CustomThreadPoolExecutor.CustomTask(new Runnable() {                @Override
                public void run() {
                    System.out.println("子线程" + Thread.currentThread().getName()
                            + "正在执行...");
                    System.out.println("子线程" + Thread.currentThread().getName()
                            + "执行完毕...");
                    countDownLatch.countDown();
                }
            }, "success");
            executorService.submit(task);
        }        try {
            System.out.println("等待2个线程...");
            countDownLatch.await();
            executorService.shutdown();
            System.out.println("2个线程执行完毕...");
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }

输出结果

等待2个线程...
子线程pool-1-thread-2正在执行...
子线程pool-1-thread-2执行完毕...
子线程pool-1-thread-1正在执行...
子线程pool-1-thread-1执行完毕...
2个线程执行完毕..

场景2

多个线程在某一个时刻同时执行。

    public static void test2() {        final CountDownLatch start = new CountDownLatch(1);        final CountDownLatch end = new CountDownLatch(10);

        ExecutorService executorService = new CustomThreadPoolExecutor(10,                10, 0L,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));        for (int i = 0; i < 10; i++) {
            CustomThreadPoolExecutor.CustomTask task = new CustomThreadPoolExecutor.CustomTask(new Runnable() {                @Override
                public void run() {                    try {
                        System.out.println("子线程" + Thread.currentThread().getName()
                                + "正在执行...");
                        start.await();
                        System.out.println("子线程" + Thread.currentThread().getName()
                                + "执行完毕...");
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    } finally {
                        end.countDown();
                    }
                }
            }, "success");
            executorService.submit(task);
        }

        start.countDown();        try {
            System.out.println("等待10个线程...");
            end.await();
            executorService.shutdown();
            System.out.println("10个线程执行完毕...");
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }

输出结果

子线程pool-1-thread-2正在执行...
子线程pool-1-thread-1正在执行...
子线程pool-1-thread-3正在执行...
子线程pool-1-thread-4正在执行...
子线程pool-1-thread-5正在执行...
子线程pool-1-thread-6正在执行...
子线程pool-1-thread-7正在执行...
等待10个线程...
子线程pool-1-thread-2执行完毕...
子线程pool-1-thread-8正在执行...
子线程pool-1-thread-8执行完毕...
子线程pool-1-thread-1执行完毕...
子线程pool-1-thread-3执行完毕...
子线程pool-1-thread-4执行完毕...
子线程pool-1-thread-10正在执行...
子线程pool-1-thread-10执行完毕...
子线程pool-1-thread-5执行完毕...
子线程pool-1-thread-6执行完毕...
子线程pool-1-thread-7执行完毕...
子线程pool-1-thread-9正在执行...
子线程pool-1-thread-9执行完毕...
10个线程执行完毕...

CountDownLatch源码分析

CountDownLatch的构造方法初始计数器值,是通过其内部类Sync的构造方法来实现的。

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

AQS中的state变量可以表示状态。对于ReentrantLock而言,代表着锁获取的次数。而对于CountDownLatch代表着计数器的值。state变量通过volatile修饰,具有可见性,可以在多个线程中共享变量。

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }

AQS中的CAS操作,使其state变量具有原子性。

    protected final boolean compareAndSetState(int expect, int update) {        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }    private static final Unsafe unsafe = Unsafe.getUnsafe();    private static final long stateOffset;    private static final long headOffset;    private static final long tailOffset;    private static final long waitStatusOffset;    private static final long nextOffset;    static {        try {
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }

我们来看一下AQS中的Node节点结构。

  • 当waitStatus = CANCELLED时,说明因为超时或者中断,节点会被设置为取消状态。处于取消状态的节点不会参与到竞争中。它会一直保持取消状态,会转变到其他状态。

  • 当waitStatus = SIGNAL时,说明当前节点的后继节点处于等待状态。而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程可以得到运行。

  • 当waitStatus = CONDITION,说明该节点在等待队列中,节点线程等待在Condition上。当其他线程对Condition调用了signal()后,该节点会从等待队列中转移到同步队列中,加入到同步状态的获取中。

  • 当waitStatus = PROPAGATE,说明下一次共享式获取同步状态,将会无条件的传播下去。

static final class Node {    /** 共享 */
    static final Node SHARED = new Node();    /** 独占 */
    static final Node EXCLUSIVE = null;    static final int CANCELLED =  1;    static final int SIGNAL    = -1;    static final int CONDITION = -2;    static final int PROPAGATE = -3;    /** 等待状态 */
    volatile int waitStatus;    /** 前驱节点 */
    volatile Node prev;    /** 后继节点 */
    volatile Node next;    /** 获取同步状态的线程 */
    volatile Thread thread;

    Node nextWaiter;    final boolean isShared() {        return nextWaiter == SHARED;
    }    final Node predecessor() throws NullPointerException {
        Node p = prev;        if (p == null)            throw new NullPointerException();        else
            return p;
    }

    Node() {
    }

    Node(Thread thread, Node mode) {        this.nextWaiter = mode;        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) {        this.waitStatus = waitStatus;        this.thread = thread;
    }
}

countDown()实际是调用AQS中的releaseShared方法中,达到计数减1的目的。

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

webp

countDown主要流程(来自于互联网).png

以共享模式去释放锁,如果tryReleaseShared方法释放锁成功,则执行AQS中的doReleaseShared方法去唤醒等待线程,并且返回true;否则返回false,说明锁释放失败。

    public final boolean releaseShared(int arg) {        if (tryReleaseShared(arg)) {
            doReleaseShared();            return true;
        }        return false;
    }

tryReleaseShared在CountDownLatch中被重写。通过轮询 + CAS方式达到释放锁的目的。第一次循环的时候判断当前state变量,如果等于0,说明计数器值为0或者说锁没有被持有,可以直接返回false。然后进行CAS操作,让获取锁的次数减少1或者说计数器值减少1。如果nextc等于0,说明计数值为0或者持有锁的次数为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;
            }
        }



作者:cmazxiaoma
链接:https://www.jianshu.com/p/fa496f7bc553


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消