CountDownLatch源码解析

CountDownLatch源码解析

描述:

一种同步辅助工具,允许一个或多个线程等待在其他线程中执行的一组操作完成。

用给定的count初始化 CountDownLatch 。因为调用了 countDown() 方法, await() 方法会一直阻塞,直到当前计数为零。在这之后,所有等待的线程都会被释放,后续对 await() 的调用都会立即返回。这是一种一次性现象 —— 计数无法重置。如果你需要一个重置计数的版本,可以考虑使用 CyclicBarrier 。

CountDownLatch 是一个通用的同步工具,可以用于许多目的。一个初始化为1的 CountDownLatch 相当于一个简单的 on/off闩锁,即门:调用 await() 的所有线程都会在门上等待,直到调用 countDown() 的线程打开门。初始化为N的 CountDownLatch 可以用来使一个线程等待,直到 N 线程完成某个操作,或者某个操作完成N次。

CountDownLatch 有一个很有用的属性,它不要求调用 countDown 的线程等待计数为0后再继续,它不要求调用 countDown 的线程在继续之前等待计数为零,它只是阻止任何线程继续执行 await(),直到所有线程都可以通过。

示例用法:

下面是两个类,其中一组工作线程使用两个倒计时锁存器:

第一个是一个启动信号,阻止任何 worker 继续前进,直到 Driver 准备好让他们继续前进;

第二个是一个完成信号,它允许 Driver 等待直到所有工人都完成工作。

class Driver { // ...
  void main() throws InterruptedException {
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(N);
    for (int i = 0; i < N; ++i) // create and start threads
      new Thread(new Worker(startSignal, doneSignal)).start();
    doSomethingElse();            // don't let run yet
    startSignal.countDown();      // let all threads proceed
    doSomethingElse();
    doneSignal.await();           // wait for all to finish
  }
}

 class Worker implements Runnable {
   private final CountDownLatch startSignal;
   private final CountDownLatch doneSignal;
   Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
     this.startSignal = startSignal;
     this.doneSignal = doneSignal;
   }
   public void run() {
     try {
       startSignal.await();
       doWork();
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
   }
   void doWork() { ... }
 }

另一种典型的用法是将一个问题划分为 N 个部分,用一个 Runnable 对象来描述每个部分,该Runnable对象执行该部分,并在闩锁时计数,然后将所有的Runnable对象排队到一个执行器。当所有子部分完成时,协调线程将能够通过await。(当线程必须以这种方式重复计数时,请使用 CyclicBarrier。)

class Driver2 { // ...
  void main() throws InterruptedException {
    CountDownLatch doneSignal = new CountDownLatch(N);
    Executor e = ...
    for (int i = 0; i < N; ++i) // create and start threads
      e.execute(new WorkerRunnable(doneSignal, i));
    doneSignal.await();           // wait for all to finish
  }
}

class WorkerRunnable implements Runnable {
  private final CountDownLatch doneSignal;
  private final int i;
  WorkerRunnable(CountDownLatch doneSignal, int i) {
    this.doneSignal = doneSignal;
    this.i = i;
  }
  public void run() {
    try {
      doWork(i);
      doneSignal.countDown();
    } catch (InterruptedException ex) {} // return;
  }
  void doWork() { ... }
}

内存一致性影响:直到计数为零,在调用线程之前执行操作

源码:

public class CountDownLatch {
    
    /**
     * CountDownLatch的同步控制。使用AQS状态表示计数
     */
    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;
            }
        }
    }

    private final Sync sync;

    /**
     * 指定计数器的构造函数
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    /**
     * 导致当前线程等待,直到闩锁计数到0,除非线程被中断。
     *
     * 1.如果当前计数为零,则此方法立即返回。
     * 2.如果当前计数大于0,那么当前线程将被禁用,用于线程调度,并处于休眠状态,直到发生以下两种情况之一: 由于调用了 countDown 方法,计数器变为0 或 其他线程中断当前线程。
     * 3.如果当前线程: 在进入此方法时设置其中断状态;或 当等待时,线程中断 , 然后 InterruptedException 被抛出,当前线程的中断状态被清除。
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    /**
     * 除非线程是被中断,或者指定的等待时间已经过,否则当前线程将等待到锁存倒计时为零。
     *
     * 1.如果当前计数为零,则该方法立即返回值 true。
     * 2.如果当前线程计数大于0,那么当前线程出于线程调度的目的被禁用,并处于休眠状态,直到发生以下三种情况之一: 由于调用了 countDown 方法,计数器变为0 或 其他线程中断当前线程。
     * 3.如果当前线程: 在进入该方法时设置了它的中断状态;或 当等待时,线程中断 。然后 InterruptedException 被抛出,当前线程的中断状态被清除。
     * 4.如果指定的等待时间过去了,则返回值  false 。如果时间小于或等于零,该方法将根本不等待。
     */
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    /**
     * 减少锁存器的计数,如果计数为零,则释放所有等待线程。
     *
     * 1.如果当前计数值大于零,则减1。如果新的计数为0,则重新启用所有等待的线程,用于线程调度。
     * 2.如果当前计数等于零,那么什么也不会发生。
     */
    public void countDown() {
        sync.releaseShared(1);
    }

    /**
     * 返回当前计数。
     */
    public long getCount() {
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

整体流程:

  1. 通过构造函数初始化 AQS 内部类的状态值 state

  2. 主线程调用 await 方法,当 state = 0 时,继续向下执行,否则进入阻塞队列,等待唤醒

  3. 子线程调用 countDown 方法,使 state-1 ,当减完之后的 state != 0 , 不做任何操作,否则是否所有等待的线程(释放主线程的阻塞)

设计思路:

主要根据 state 的值是否大于 0 ,

大于0主线程是否阻塞,当子线程逐步做state的减一操作,state = 0 时,释放主线程,主线程继续执行。以达到计数器到零之后再继续操作

小于等于0,主线程不阻塞,继续执行

posted @ 2023-02-17 07:06  Cool_Yang  阅读(39)  评论(0编辑  收藏  举报