程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java CountDownLatch解析(下),javacountdownlatch

Java CountDownLatch解析(下),javacountdownlatch

編輯:JAVA綜合教程

Java CountDownLatch解析(下),javacountdownlatch


  • 寫在前面的話

在上一篇CountDownLatch解析中,我們了解了CountDownLatch的簡介、CountDownLatch實用場景、CountDownLatch實現原理中的await()方法,

接下來我們接著來了解它的countDown()方法以及它的示例和優缺點。

  • CountDownLatch實現原理

二、CountDownLatch.countDown() 

  關於同步隊列那點事

當部分線程調用await()方法後,它們在同步隊列中被掛起,然後自省的檢查自己能否滿足醒來的條件(還記得那個條件嗎?1、state為0,2、該節點為頭節點),

如果滿足它將被移除頭節點,並將下一個節點設置為頭節點。具體如下圖:

這個Node是AbstractQueuedSynchronizer(下文簡稱AQS)中的靜態內部類,其中它又有兩個屬性:

volatile Node prev;

volatile Node next;

volatile的prev指向上一個node節點,volatile的next指向下一個node節點。當然如果是頭節點,那麼它的prev為null,同理尾節點的next為null。

private transient volatile Node head;

private transient volatile Node tail;

head和tail是AQS中的屬性,它們用來表示同步隊列的頭節點和尾節點。(有興趣的同學可以引申看一下 transident 這個關於序列化關鍵字,這裡不展開啦=_=)

了解了同步隊列後,接著我們開始線程調用countDown()方法後,到底發生了什麼事?這兒咱們可以想象一下,可能計數值會減去1,而且還會判斷state是不是等於0,

如果不等於0,這個線程就繼續往下走了;如果等於0,那麼它可能還需要去叫醒這群掛起的線程。

到底是不是這樣呢,別方,跟著筆者一起繼續來扒源碼。

public void countDown() {
   sync.releaseShared(1);
}

當調用CountDownLatch.countDown()後,它轉而調用了Sync這個內部類實例的releaseShared()方法。

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;     //退出該方法
    }
    return false;        //退出該方法
}

在Sync類中並沒有releaseShared()方法,所以應該是繼承與AQS,咱們看到AQS這個方法中,退出該方法的只有兩條路。tryReleaseShared(arg)條件為真執行一個doReleaseShared()退出;條件為假直接退出。

類比我們的猜測,這個條件很有可能就是state是否為0的判斷。。。接著來看。

protected boolean tryReleaseShared(int releases) {
    for (;;) {//死循環
        int c = getState();// 獲取主存中的state值
        if (c == 0) //state已經為0 直接退出
            return false;
        int nextc = c-1; // 減一 准備cas更新該值
        if (compareAndSetState(c, nextc)) //cas更新
            return nextc == 0; //更新成功 判斷是否為0 退出;更新失敗則繼續for循環,直到線程並發更新成功
    }
}

看到這兒四不四靈光從腦子噴湧而出啦。我們的猜測是正確的!

private void doReleaseShared() {
    for (;;) {//又是一個死循環
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {//如果當前節點是SIGNAL意味著,它正在等待一個信號,或者說它在等待被喚醒,因此做兩件事,1是重置waitStatus標志位,2是重置成功後,喚醒下一個節點。
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            
                unparkSuccessor(h);
            }else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))//如果本身頭節點的waitStatus是出於重置狀態(waitStatus==0)的,將其設置為“傳播”狀態。意味著需要將狀態向後一個節點傳播。
                continue;                
        }
        if (h == head)                   
            break;
    }
}

同學們(敲黑板...),我們為啥要執行這個方法呀,因為state已經為0啦,我們該將同步隊列中的線程狀態設置為共享狀態(Node.PROPAGATE,默認狀態ws == 0),並向後傳播,實現狀態共享。

這就是為啥方法名有個shared,因為這個共享狀態需要傳播下去,而不是一個節點(線程)獨占。看看這個死循環,退出的路只有一條,那就是h==head,即該線程是頭節點,且狀態為共享狀態。

 這裡再啰嗦一句,可能讀者會問,state已經等於0了,我們也通過循環的方式把頭節點的狀態設置為共享狀態,但是它怎麼醒過來的呢?既然這樣,那我們再看一次上一篇中的代碼

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);// 往同步隊列中添加節點
    boolean failed = true;
    try {
        for (;;) {// 一個死循環 跳出循環只有下面兩個途徑
            final Node p = node.predecessor();// 當前線程的前一個節點
            if (p == head) {// 如果是首節點
                int r = tryAcquireShared(arg);// 這個是不是似曾相識 見上面
                if (r >= 0) {
                    setHeadAndPropagate(node, r);// 處理後續節點
                    p.next = null; // help GC 這個可以借鑒
                    failed = false;
                    return;// 計數值為0 並且為頭節點 跳出循環
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();// 響應打斷 跳出循環
        }
    } finally {
        if (failed)
            cancelAcquire(node);// 如果是打斷退出的 則移除同步隊列節點
    }
}

這個就是在同步隊列中掛起的線程,它們自旋的形式查看自己是否滿足條件醒來(state==0,且為頭節點),如果成立將調用setHeadAndPropagate這個方法

 private void setHeadAndPropagate(Node node, int propagate) {
     Node h = head; // Record old head for check below
     setHead(node);
     if (propagate > 0 || h == null || h.waitStatus < 0 ||
         (h = head) == null || h.waitStatus < 0) {
         Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

這個方法是將當前節點的下一個節點設置為頭節點,且它也調用了doReleaseShared這個方法,我們剛才也說了,這個方法就是將頭節點設置為共享狀態的,由此,共享狀態傳播下去。

至此,CountDownLatch實現原理中的countDown()方法剖析結束。

  • CountDownLatch示例

廢話不多說,直接上代碼:

package com.test.demo;

import java.util.concurrent.CountDownLatch;

/** 
* @Title: TestCountDownLatch.java
* @Describe:
* @author: Mr.Yanphet 
* @Email: [email protected]
* @date: 2016年9月18日 上午11:22:42  
* @version: 1.0 
*/
public class TestCountDownLatch {
    
    private static final int taskNum = 5;
    
    static class MyRunnable implements Runnable {
        
        private int num;
        
        private CountDownLatch cdl;
        
        public MyRunnable(int num, CountDownLatch cdl){
            this.num = num;
            this.cdl = cdl;
        }
        
        public void run() {
            System.out.println("第" + num + "個線程開始執行任務...");
            try {
                Thread.sleep(5 * 1000); // 模擬任務耗時
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第" + num + "個線程任務執行結束...");
            cdl.countDown();
        }
    }
    
    public static void main(String[] args) {
        CountDownLatch cdl = new CountDownLatch(taskNum);
        for (int i = 1; i <= taskNum; i++) {
            MyRunnable mr = new MyRunnable(i, cdl);
            Thread t = new Thread(mr);
            t.start();
        }
        System.out.println("等待其他線程完成任務才繼續執行...");
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("其他線程完成任務,主線程開始執行...");
        System.out.println("主線程任務完成,整個任務進度完成...");
    }

}

執行結果如下:

第4個線程開始執行任務...
第5個線程開始執行任務...
第3個線程開始執行任務...
第2個線程開始執行任務...
第1個線程開始執行任務...
等待其他線程完成任務才繼續執行...
第4個線程任務執行結束...
第2個線程任務執行結束...
第1個線程任務執行結束...
第3個線程任務執行結束...
第5個線程任務執行結束...
其他線程完成任務,主線程開始執行...
主線程任務完成,整個任務進度完成...

主線程等待5個子線程執行完任務,才繼續往下執行自己的任務。

  • CountDownLatch的優缺點

優點:

CountDownLatch的優點毋庸置疑,對使用者而言,你只需要傳入一個int型變量控制任務數量即可,至於同步隊列的出隊入隊維護,state變量值的維護對使用者都是透明的,使用方便。

缺點:

CountDownLatch設置了state後就不能更改,也不能循環使用。

以上就是關於CountDownLatch學習的全部內容。因為筆者也是菜鳥,所以站在菜鳥的角度分析源碼,難免重復啰嗦。如有任何問題,希望大家指正。謝謝~~~

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved