前言併發
前面四節學完了AQS最難的兩種重入鎖應用,下面兩節進入實戰學習,看看JUC包中其餘的工具類是如何運用AQS實現特定功能的。今天一塊兒看一下CountDownLatch。工具
CountDownLatch能夠用來實現多個線程執行完一個功能後讓另外一個線程繼續執行的功能。常見的場景好比大文件的處理,咱們須要對一個或多個文件進行處理,處理完以後再統一入庫,這時咱們就能夠用到CountDownLatch了。學習
1、使用樣例ui
1 public static void main(String[] args) { 2 // 指定初始容量 3 CountDownLatch latch = new CountDownLatch(3); 4 // 啓動三個線程,每一個線程獨自處理文件 5 for (int i = 0;i < 3; i++) { 6 new Thread(() -> { 7 System.out.println(Thread.currentThread().getName() + " 正在處理文件"); 8 try { 9 Thread.sleep(2000); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 System.out.println(Thread.currentThread().getName() + " 處理完畢"); 14 latch.countDown(); 15 }).start(); 16 } 17 try { 18 latch.await(); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 System.out.println("全部文件處理完成後,統一入庫"); 23 }
運行結果:this
1 Thread-0 正在處理文件 2 Thread-2 正在處理文件 3 Thread-1 正在處理文件 4 Thread-0 處理完畢 5 Thread-2 處理完畢 6 Thread-1 處理完畢 7 全部文件處理完成後,統一入庫
效果就是這樣,下面咱們一塊兒看看它是如何實現的這種功能。spa
2、源碼學習線程
一、首先咱們看看new CountDownLatch(3) 作了什麼事情code
1 public CountDownLatch(int count) { 2 if (count < 0) throw new IllegalArgumentException("count < 0"); 3 this.sync = new Sync(count); 4 }
繼續追蹤能夠發現,就是將count賦值給AQS中的成員變量state,表示已經有3個線程佔用了鎖。blog
二、看countDown()方法作了什麼源碼學習
1 public void countDown() { 2 sync.releaseShared(1); 3 }
能夠看到,countDown走的是釋放共享鎖的邏輯,從給state賦值也能夠猜到用的是共享鎖-有多個線程且state可賦大於0的值。繼續看releaseShared邏輯:
1 public final boolean releaseShared(int arg) { 2 if (tryReleaseShared(arg)) { 3 doReleaseShared(); 4 return true; 5 } 6 return false; 7 }
能夠看到就是讀鎖釋放的邏輯,其中doReleaseShared方法實現邏輯相同就不看了,不一樣的是tryReleaseShared方法,下面跟進:
1 protected boolean tryReleaseShared(int releases) { 2 // Decrement count; signal when transition to zero 3 for (;;) { 4 int c = getState(); 5 if (c == 0) 6 return false; 7 int nextc = c-1; 8 if (compareAndSetState(c, nextc)) 9 return nextc == 0; 10 } 11 }
此方法在CountDownLatch中的內部類Sync中獲得實現,邏輯爲將state-1,而且若是是0的話返回true。返回true後在releaseShared方法中會進入if裏面,走喚醒後續節點的邏輯doReleaseShared方法,在該方法中喚醒的main線程。main線程何時被掛起的?且看下面。
三、await方法
1 public void await() throws InterruptedException { 2 sync.acquireSharedInterruptibly(1); 3 }
await調用了可響應中斷的獲取共享鎖方法,繼續查看:
1 public final void acquireSharedInterruptibly(int arg) 2 throws InterruptedException { 3 if (Thread.interrupted()) 4 throw new InterruptedException(); 5 if (tryAcquireShared(arg) < 0) 6 doAcquireSharedInterruptibly(arg); 7 }
此方法是AQS中的公用模板方法,不一樣點在於各實現類的實現邏輯,在CountDownLatch中對tryAcquireShared方法進行了實現,實現邏輯以下:
1 protected int tryAcquireShared(int acquires) { 2 return (getState() == 0) ? 1 : -1; 3 }
即若是state==0則能獲取到鎖,不然獲取不到。獲取不到進入下面的doAcquireSharedInterruptibly方法,最終會將head的waitStatus設置爲-1,本身掛起等待喚醒。
3、總結
CountDownLatch是基於共享鎖實現的併發控制功能,如今對總的實現邏輯作個梳理:首先在構造器初始化CountDownLatch的時候,就會給AQS中的state賦值,表示共享鎖已經被獲取了N次;而後每執行一次countDown則共享鎖釋放一次,直到釋放完;await方法是加鎖的邏輯,但加鎖條件是state==0時纔會加鎖成功,不然掛起;最後,當經過countDown的調用將state減爲0後,會喚醒處於阻塞狀態的主線程,讓其 獲取到鎖並執行。