之前在刷题的时候有遇到这样一个编程题:100个人同时赛跑,得到前十名的排行榜。可谓是抓耳挠腮,不知怎么办。后面接触了并发类Countdownlatch,作一个demo记录该如何使用Countdownlatch。
Countdownlatch是利用计数器来实现并发开始、结束的,在构造方法中需要设置计数器大小,每次一个线程执行完毕就将计数器减1,当这个计数器
为0时,就会继续往下执行,否则等待。
构造函数及内部类Sync (这个Sync看到后面越看越懵。2020-07-02更新:CAS懂了,AQS还是懵的):
//内部类,await,countdown都是基于它实现的 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; /** * 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); }
现在我们来尝试100个人同时赛跑,然后打印他们完成的顺序。定义一个begin,用来记录同时开始,计数器为1。再定义一个end,计数器为100,
用来记录结束的标志。线程状态为:
- 当for循环执行完毕,这个时候begin的计数器为1,begin还在await等待,100个人在预备。
- begin.countdown,begin的计数器变为0,begin不再await,100个人开始赛跑。
- 每个人跑完,end就执行一个countdown,100,99,98....
- end的计数器变为0,主线程main继续往下执行。
public static void main(String[] args){ CountDownLatch begin = new CountDownLatch(1); //1.用来同时开始,如果不需要同时开始,那么就不需要这个。 CountDownLatch end = new CountDownLatch(100); //2.用来结束标记,当计数器减为0的时候,就继续往下执行 for (int i=0;i<100;i++){ ThreadTest threadTest = new ThreadTest(begin,end); threadTest.start(); } try{ System.out.println("begin...."); begin.countDown(); //3.所有线程在等待了,见4。减1后,4处等待的线程开始执行 end.await(); //4.等待5处所有线程都减1,即计数器变为0,就继续往下执行 System.out.println("end...."); }catch (Exception e){ } } static class ThreadTest extends Thread{ CountDownLatch begin; CountDownLatch end; ThreadTest(CountDownLatch begin,CountDownLatch end){ this.begin=begin; this.end=end; } public void run(){ try{ begin.await(); //4.所有线程等待,因为begin的计数器还是1 // Thread.sleep(5000); for(int i=1;i<1000;i++){ i=i*i; } System.out.println(Thread.currentThread().getName()+"ok"); end.countDown(); //5.线程执行完了,end的计数器就减1 }catch (Exception e){ } } }
Countdownlatch使用就这么简单不看Sync原理的话,await还有另一种方式,可设置等待时间,防止线程里运行时间过久影响主线程,或死锁的发生。
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); }