Java 多線程編程之八:多線程的調度

來源:互聯網
上載者:User

        本部落格是“Java 多線程編程”系列的後續篇。“Java 多線程編程”系列其他部落格請參閱本部落格結尾部分。
        有多個線程,如何控制它們執行的先後次序?
        方法一:設定線程優先順序
        java.lang.Thread 提供了 setPriority(int newPriority) 方法來設定線程的優先順序,但線程的優先順序是無法保障線程的執行次序的,優先順序只是提高了優先順序高的線程擷取 CPU 資源的機率。也就是說,這個方法不靠譜。
        方法二:使用線程合并
        使用 java.lang.Thread 的 join() 方法。比如有線程 a,現在當前線程想等待 a 執行完之後再往下執行,那就可以使用 a.join()。一旦線程使用了 a.join(),那麼當前線程會一直等待 a 消亡之後才會繼續執行。什麼時候 a 消亡?a 的 run() 方法執行結束了 a 就消亡了。
        這個方法可以有效地進行線程調度,但卻只能局限於等待一個線程的執行調度。如果要等待 N 個線程的話,顯然是無能為力了。而且等待線程必須在被等待線程消亡後才得到繼續執行的指令,無法做到兩個線程真正意義上的並發,靈活性較差。
        方法三:使用線程通訊
        java.lang.Object 提供了可以進行線程間通訊的 wait 與 notify 、notifyAll 等方法。每個 Java 對象都有一個隱性的線程鎖的概念,通過這個線程鎖的概念我們讓線程間可以進行通訊,各線程不再埋頭單幹。著名的“生產者-消費者”模型就是基於這個原理實現的。
        這個方法也可以有效地進行線程調度,而且也不僅僅局限於等待一個線程的執行調度,具有很大程度上的靈活性。但操作複雜,不易控制容易造成混亂,程式維護起來也不太方便。
        方法四:使用閉鎖
        閉鎖就像一扇門,在先決條件未達成之前這扇門是閉著的,線程無法通過,先決條件達成之後,閉鎖開啟,線程就可以繼續執行了。java.util.concurrent.CountDownLatch 是一個很實用的閉鎖實現,它提供了 countDown() 和 await() 方法達成線程執行隊列,這個方法最適合 M 個線程等待 N 個線程執行結束再執行的情況。首先初始化一個 CountDownLatch 對象,比如 CountDownLatch doneSignal = new CountDownLatch(N);該對象具有
N 作為計數閥值,每個被等待線程通過對 doneSignal 對象的持有,使用 countDown() 可以將 doneSignal 的計數閥值減一;每個等待線程通過對 doneSignal 對象的持有,使用 await() 阻塞當前線程,直到 doneSignal 計數閥值減為 0,才繼續往下執行。
        這個方法也可以有效地進行線程調度,而且比方法三更易於管理,開發人員只需控制好 CountDownLatch 即可。但線程執行次序管理相對單一,它只是指出當前等待線程的數量,而且 CountDownLatch 的初始閥值一旦設定就只能遞減下去,無法重設。如需遞減過程中進行閥值的重設可以參考 java.util.concurrent.CyclicBarrier。
        不管如何,CountDownLatch 對於一定條件下的線程隊列的達成還是很有用的。對於複雜環境下的線程管理還是卓有成效的。所以熟悉和把握對它的使用還是很有必要的。

        以下是一個實際項目中 CountDownLatch 的使用的例子:

private Map<Long,DecryptSignalAndPath> afterDecryptFilePathMap = new HashMap<Long,DecryptSignalAndPath>();//TODO 注意容器垃圾資料的清理工作class DecryptRunnable implements Runnable {private ServerFileBean serverFile;private Long fid;//指向解密檔案private CountDownLatch decryptSignal;protected DecryptRunnable(Long fid, ServerFileBean serverFile, CountDownLatch decryptSignal) {this.fid = fid;this.serverFile = serverFile;this.decryptSignal = decryptSignal;}@Overridepublic void run() {//開始解密String afterDecryptFilePath = null;DecryptSignalAndPath decryptSignalAndPath = new DecryptSignalAndPath();decryptSignalAndPath.setDecryptSignal(decryptSignal);afterDecryptFilePathMap.put(fid, decryptSignalAndPath);afterDecryptFilePath = decryptFile(serverFile);decryptSignalAndPath.setAfterDecryptFilePath(afterDecryptFilePath);decryptSignal.countDown();//通知所有阻塞的線程}}class DecryptSignalAndPath {private String afterDecryptFilePath;private CountDownLatch decryptSignal;public String getAfterDecryptFilePath() {return afterDecryptFilePath;}public void setAfterDecryptFilePath(String afterDecryptFilePath) {this.afterDecryptFilePath = afterDecryptFilePath;}public CountDownLatch getDecryptSignal() {return decryptSignal;}public void setDecryptSignal(CountDownLatch decryptSignal) {this.decryptSignal = decryptSignal;}}

        需要先執行的,被等待線程在這裡加入:

CountDownLatch decryptSignal = new CountDownLatch(1);new Thread(new DecryptRunnable(fid, serverFile, decryptSignal)).start();//無需拿到新線程控制代碼,由 CountDownLatch 自行跟蹤try {decryptSignal.await();} catch (InterruptedException e) {// TODO Auto-generated catch block}

        需要後執行,等待的線程可以這樣加入:

CountDownLatch decryptSignal = afterDecryptFilePathMap.get(fid).getDecryptSignal();try {decryptSignal.await();} catch (InterruptedException e) {// TODO Auto-generated catch block}

        當然,這也僅僅只是一個簡單的 CountDownLatch 的使用展示,對於 CountDownLatch 來說有點大材小用了,因為它可以勝任更複雜的多線程環境。樣本中的案例完全可以使用線程通訊進行搞定。因為 CountDownLatch 的閥值初始為 1,所以這裡甚至完全可以使用方法二所說的線程的合并進行取代。

        如果讀者覺得以上樣本不夠清晰,也可以參考 JDK API 提供的 demo,這個清晰明了:

 class Driver2 { // ...   void main() throws InterruptedException {     CountDownLatch doneSignal = new CountDownLatch(N);     Executor e = ...     for (int i = 0; i < N; ++i) // create and start threads       e.execute(new WorkerRunnable(doneSignal, i));     doneSignal.await();           // wait for all to finish   } } class WorkerRunnable implements Runnable {   private final CountDownLatch doneSignal;   private final int i;   WorkerRunnable(CountDownLatch doneSignal, int i) {      this.doneSignal = doneSignal;      this.i = i;   }   public void run() {      try {        doWork(i);        doneSignal.countDown();      } catch (InterruptedException ex) {} // return;   }   void doWork() { ... } }

        “Java 多線程編程”系列其他部落格:
       
Java 多線程編程之一 進程與線程,並發和並行的區別:吃饅頭的比喻
       
Java 多線程編程之二 volatile 關鍵字的使用
       
Java 多線程編程之三:synchronized 關鍵字的使用
       
Java 多線程編程之四:擷取 Java VM 中當前啟動並執行所有線程
       
Java 多線程編程之五:一個理解 wait() 與 notify() 的例子
       
Java 多線程編程之六:線程之間的通訊(附原始碼)
       
Java 多線程編程之七:死結(附原始碼)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.