java并发:线程协作机制之CountDownLatch

一、初识CountDownLatch

(1)概述

(2)应用场景

Application’s main thread wants to wait, till other service threads which are responsible for starting framework services have completed started all services.

二、详述CountDownLatch

CountDownLatch是通过一个计数器来实现的。

每当一个线程完成了自己的任务后,计数器的值就会减1,当计数器值到达0时,它表示所有的线程已经完成了任务,然后处于等待的线程就可以恢复执行任务。

下图展示了CountDownLatch中的方法:

  • public CountDownLatch(int count)

  构造函数,count(计数器)实际上就是需要等待的线程数量,这个值只能被设置一次(即:CountDownLatch没有提供任何机制去重新设置这个值)。

  • public void countDown()

  每调用一次这个方法,在构造函数中初始化的count值就减1。

  • public void await() throws InterruptedException

  调用此方法的线程会一直阻塞,直到计时器的值为0。

 

下图展示了CountDownLatch中内部类Sync的类图:

解释:

根据类图即可知道Sync继承了AbstractQueuedSynchronizer,进而可知CountDownLatch是使用 AQS 实现的。

 

构造函数

CountDownLatch的构造函数的定义如下:

    /**
     * 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);
    }

解读:

构造函数创建了内部类Sync的实例。

 

Sync的定义如下:

    /**
     * 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;
            }
        }
    }

 

解读:

通过上述代码可以发现,计数器count实际上是赋值给了 AQS 的状态变量 state。

 

await方法

其定义如下:

    /**
     * Causes the current thread to wait until the latch has counted down to
     * zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
     *
     * <p>If the current count is zero then this method returns immediately.
     *
     * <p>If the current count is greater than zero then the current
     * thread becomes disabled for thread scheduling purposes and lies
     * dormant until one of two things happen:
     * <ul>
     * <li>The count reaches zero due to invocations of the
     * {@link #countDown} method; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread.
     * </ul>
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * @throws InterruptedException if the current thread is interrupted
     *         while waiting
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

解读:

上述方法实际上是调用了父类AbstractQueuedSynchronizer中的acquireSharedInterruptibly方法,其定义如下:

    /**
     * Acquires in shared mode, aborting if interrupted.  Implemented
     * by first checking interrupt status, then invoking at least once
     * {@link #tryAcquireShared}, returning on success.  Otherwise the
     * thread is queued, possibly repeatedly blocking and unblocking,
     * invoking {@link #tryAcquireShared} until success or the thread
     * is interrupted.
     * @param arg the acquire argument.
     * This value is conveyed to {@link #tryAcquireShared} but is
     * otherwise uninterpreted and can represent anything
     * you like.
     * @throws InterruptedException if the current thread is interrupted
     */
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

解读:

根据AQS的实现原理,Sync需要实现tryAcquireShared方法。

请参见本文前面Sync的定义,Sync 中的 tryAcquireShared 方法查看当前状态值(计数器值)是否为 0,是则直接返回,否则调用 AQS 的 doAcquireSharedlnterruptibly 方法让当前线程阻塞。

 

countDown方法

    /**
     * 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);
    }

解读:

上述方法实际上是调用了父类AbstractQueuedSynchronizer中的releaseShared方法,其定义如下:

    /**
     * Releases in shared mode.  Implemented by unblocking one or more
     * threads if {@link #tryReleaseShared} returns true.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryReleaseShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     * @return the value returned from {@link #tryReleaseShared}
     */
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

解读:

根据AQS的实现原理,Sync需要实现tryReleaseShared方法。

请参见本文前面Sync的定义,Sync 中的 tryReleaseShared 方法首先获取当前状态值(计数器值) ,如果当前状态值为 0 则直接返回 false;否则使用 CAS 将计数器值减 1(在此过程中,CAS 如果失败则循环重试),如果计数器值被减为 0 则返回 true 以调用 AQS 的 doReleaseShared 方法来激活阻塞的线程。


 

 

三、示例

package com.test;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo{
    
    public static void main(String args[]) throws Exception{
        CountDownLatch latch = new CountDownLatch(3);
        Worker worker1 = new Worker("Jack 程序员1",latch);
        Worker worker2 = new Worker("Rose 程序员2",latch);
        Worker worker3 = new Worker("Json 程序员3",latch);
        worker1.start();
        worker2.start();
        worker3.start();
        
        latch.await();
        System.out.println("Main thread end!");
    }
    
    static class Worker extends Thread {
        private String workerName;
        private CountDownLatch latch;
        public Worker(String workerName,CountDownLatch latch) {
            this.workerName = workerName;
            this.latch = latch;
        }
        @Override
        public void run() {
            try {
                System.out.println("Worker:"+workerName +" is begin.");
                Thread.sleep(1000L);
                System.out.println("Worker:"+workerName +" is end.");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }//模仿干活;
            latch.countDown();
        }
    }
}

上述程序运行结果如下:

Worker:Rose 程序员2 is begin.
Worker:Json 程序员3 is begin.
Worker:Jack 程序员1 is begin.
Worker:Jack 程序员1 is end.
Worker:Json 程序员3 is end.
Worker:Rose 程序员2 is end.
Main thread end!

从结果上可以看出,MainThread执行到latch.await();处会阻塞在该处,直到三个线程均完成的时候MainThread才会继续往下执行 

Note:

Other N threads must have a reference of the latch object because they will need to notify the CountDownLatch object that they have completed their task.

四、分析总结

问题:CountDownLatch与join方法的区别

在线程中调用某个子线程的join() 方法后,该线程将一直被阻塞直到子线程运行完毕,而 CountDownLatch通过使用计数器来使得子线程在运行完或者运行中递减计数,所以 CountDownLatch可以在子线程运行的任何时候让await方法返回,并不需要等到线程结束。

另外,使用线程池来管理线程时一般都是直接添加 Runable 到线程池,这时没有办法调用线程的 join 方法,所以 countDownLatch 相比 join 方法更灵活。

 

(1)https://howtodoinjava.com/java/multi-threading/when-to-use-countdownlatch-java-concurrency-example-tutorial/

posted @ 2016-04-15 19:20  时空穿越者  阅读(3786)  评论(0编辑  收藏  举报