之前在刷題的時候有遇到這樣一個編程題: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)); }