11、CountDownLatch
java.util.concurrent
Class CountDownLath
使一个线程或多个线程等待另外一个线程或多个线程操作的完成。
CountDownLath以一个给定的数字初始化。await方法一直阻塞直到当前这个数字由于其他线程运行countDown方法将初始化的数字减为0,阻塞等待的线程才能被唤醒执行。
这是一个一次性的现象,这个数字不能够重置。如果需要一个重复利用的版本,应该考虑使用CyclicBarrier。
一个CountDownLatch被初始化N,那么就会使得线程在await那里全部阻塞,直到有N个线程运行了CountDownLatch使得N变成了0,这时候阻塞的线程能够被唤醒执行。
1 class Driver { 2 void main() throws InterruptedException { 3 CountDownLatch startSignal = new CountDownLatch(1); 4 CountDownLatch doneSignal = new CountDownLatch(N); 5 6 for (int i = 0; i < N; ++i) // 创建和启动线程 7 new Thread(new Worker(startSignal, doneSignal)).start(); 8 9 doSomethingElse(); 10 startSignal.countDown(); // 让所有的线程处理12 doneSignal.await(); // 等待所有的线程完成
doSomethingOther()
13 } 14 } 15 16 17 class Worker implements Runnable { 18 private final CountDownLatch startSignal; 19 private final CountDownLatch doneSignal; 20 Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { 21 this.startSignal = startSignal; 22 this.doneSignal = doneSignal; 23 } 24 public void run() { 25 try { 26 startSignal.await(); 27 doWork(); 28 doneSignal.countDown(); 29 } catch (InterruptedException ex) {} // return; 30 } 31 32 void doWork() { ... } 33 } 34
程序分析:
上面的程序是一个驱动程序的框架。假设是这么一个流程:驱动刚开始要获得某些资源(doSomethingElse);然后进行一些异步的初始化工作(doWork);最后为最终运行的软件做一些准备(doSomethingOther)。
这三个步骤是同步的。第二个步骤为了提高效率可以用多个线程去进行。有三个步骤,三个步骤要同步,于是有两个同步点。第一个同步点就是要在第一个步骤完成之后,第一个步骤只有一个线程完成,因此设置了
startSingal;第二个同步点在第二个步骤之后,这个步骤的完成需要N个线程,因此设置了doneSingal。
三个步骤在同一个main线程中,虽然第二个步骤可能起步比较早(产生N个线程准备去进行一些初始化工作),但是他们都会阻塞在在一个同步点处,也就是startSingal.await处。当doSomethingElse方法完成之后,调用了
startSingal.countDown方法,此时startSingal的数字被减为0(也表示第一个阶段已经完成),于是N个阻塞与第一个同步点的线程都被唤醒去执行异步初始化工作(doWork)。当然这些初始化工作的时长又长又短,但是
先被执行完的会阻塞于第二个同步点downSingal.await。直到N个线程完成了(doWork),就会执行最后一个步骤。
1 class Driver2 { 2 void main() throws InterruptedException { 3 CountDownLatch doneSignal = new CountDownLatch(N); 4 Executor e = ... 5 6 for (int i = 0; i < N; ++i) 7 e.execute(new WorkerRunnable(doneSignal, i)); 8 9 doneSignal.await(); 10 } 11 } 12 13 class WorkerRunnable implements Runnable { 14 private final CountDownLatch doneSignal; 15 private final int i; 16 WorkerRunnable(CountDownLatch doneSignal, int i) { 17 this.doneSignal = doneSignal; 18 this.i = i; 19 } 20 public void run() { 21 try { 22 doWork(i); 23 doneSignal.countDown(); 24 } catch (InterruptedException ex) {} return; 25 } 26 27 void doWork() { ... } 28 }
程序分析:
上面这个程序框架就是一个典型算法“动态规划”的代码框架。它将问题分解成N个子问题,然后每一部分就是一个Runnable任务并且执行完一个任务就会使得数字减一,我们可以
将所有的任务以队列的形式组织到Executor当中。直到所有的子问题全部完成,协调线程会一直处于阻塞状态。