摘要:本文使用了7中方法實現在多線程中讓線程按順序運行的方法,主要目的是讓讀者對多線程的使用有更深刻的瞭解。 ...
轉載請註明出處:https://www.cnblogs.com/wenjunwei/p/10573289.html
一.前言
本文使用了7中方法實現在多線程中讓線程按順序運行的方法,涉及到多線程中許多常用的方法,不止為了知道如何讓線程按順序運行,更是讓讀者對多線程的使用有更深刻的瞭解。 使用的方法如下:
- [1] 使用線程的join方法
- [2] 使用主線程的join方法
- [3] 使用線程的wait方法
- [4] 使用線程的線程池方法
- [5] 使用線程的Condition(條件變數)方法
- [6] 使用線程的CountDownLatch(倒計數)方法
- [7] 使用線程的CyclicBarrier(迴環柵欄)方法
- [8] 使用線程的Semaphore(信號量)方法
二.實現
我們下麵需要完成這樣一個應用場景:
1.早上;2.測試人員、產品經理、開發人員陸續的來公司上班;3.產品經理規劃新需求;4.開發人員開發新需求功能;5.測試人員測試新功能。
規劃需求,開發需求新功能,測試新功能是一個有順序的,我們把thread1看做產品經理,thread2看做開發人員,thread3看做測試人員。
1.使用線程的join方法
join():是Theard的方法,作用是調用線程需等待該join()線程執行完成後,才能繼續用下運行。
應用場景:當一個線程必須等待另一個線程執行完畢才能執行時可以使用join方法。
package com.wwj.javabase.thread.order; /** * @author wwj * 通過子程式join使線程按順序執行 */ public class ThreadJoinDemo { public static void main(String[] args) { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("產品經理規劃新需求"); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { thread1.join(); System.out.println("開發人員開發新需求功能"); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { thread2.join(); System.out.println("測試人員測試新功能"); } catch (InterruptedException e) { e.printStackTrace(); } } }); System.out.println("早上:"); System.out.println("測試人員來上班了..."); thread3.start(); System.out.println("產品經理來上班了..."); thread1.start(); System.out.println("開發人員來上班了..."); thread2.start(); } }
運行結果
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
2.使用主線程的join方法
這裡是在主線程中使用join()來實現對線程的阻塞。
package com.wwj.javabase.thread.order; /** * @author wwj * 通過主程式join使線程按順序執行 */ public class ThreadMainJoinDemo { public static void main(String[] args) throws Exception { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("產品經理正在規劃新需求..."); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println("開發人員開發新需求功能"); } }); final Thread thread3 = new Thread(new Runnable() { @Override public void run() { System.out.println("測試人員測試新功能"); } }); System.out.println("早上:"); System.out.println("產品經理來上班了"); System.out.println("測試人員來上班了"); System.out.println("開發人員來上班了"); thread1.start(); //在父進程調用子進程的join()方法後,父進程需要等待子進程運行完再繼續運行。 System.out.println("開發人員和測試人員休息會..."); thread1.join(); System.out.println("產品經理新需求規劃完成!"); thread2.start(); System.out.println("測試人員休息會..."); thread2.join(); thread3.start(); } }
運行結果
產品經理來上班了
測試人員來上班了
開發人員來上班了
開發人員和測試人員休息會...
產品經理正在規劃新需求...
產品經理新需求規劃完成!
測試人員休息會...
開發人員開發新需求功能
測試人員測試新功能
3.使用線程的wait方法
wait():是Object的方法,作用是讓當前線程進入等待狀態,同時,wait()也會讓當前線程釋放它所持有的鎖。“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)
notify()和notifyAll():是Object的方法,作用則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒所有的線程。
wait(long timeout):讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的notify()方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
應用場景:Java實現生產者消費者的方式。
package com.wwj.javabase.thread.order; /** * @author wwj */ public class ThreadWaitDemo { private static Object myLock1 = new Object(); private static Object myLock2 = new Object(); /** * 為什麼要加這兩個標識狀態? * 如果沒有狀態標識,當t1已經運行完了t2才運行,t2在等待t1喚醒導致t2永遠處於等待狀態 */ private static Boolean t1Run = false; private static Boolean t2Run = false; public static void main(String[] args) { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { synchronized (myLock1){ System.out.println("產品經理規劃新需求..."); t1Run = true; myLock1.notify(); } } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { synchronized (myLock1){ try { if(!t1Run){ System.out.println("開發人員先休息會..."); myLock1.wait(); } synchronized (myLock2){ System.out.println("開發人員開發新需求功能"); myLock2.notify(); } } catch (InterruptedException e) { e.printStackTrace(); } } } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { synchronized (myLock2){ try { if(!t2Run){ System.out.println("測試人員先休息會..."); myLock2.wait(); } System.out.println("測試人員測試新功能"); } catch (InterruptedException e) { e.printStackTrace(); } } } }); System.out.println("早上:"); System.out.println("測試人員來上班了..."); thread3.start(); System.out.println("產品經理來上班了..."); thread1.start(); System.out.println("開發人員來上班了..."); thread2.start(); } }
運行結果:這裡輸出會有很多種順序,主要是因為線程進入的順序,造成鎖住線程的順序不一致。
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
測試人員先休息會...
產品經理規劃新需求...
開發人員開發新需求功能
測試人員測試新功能
4.使用線程的線程池方法
JAVA通過Executors提供了四種線程池
- 單線程化線程池(newSingleThreadExecutor);
- 可控最大併發數線程池(newFixedThreadPool);
- 可回收緩存線程池(newCachedThreadPool);
- 支持定時與周期性任務的線程池(newScheduledThreadPool)。
單線程化線程池(newSingleThreadExecutor):優點,串列執行所有任務。
submit():提交任務。
shutdown():方法用來關閉線程池,拒絕新任務。
應用場景:串列執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
package com.wwj.javabase.thread.order; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author wwj * 通過SingleThreadExecutor讓線程按順序執行 */ public class ThreadPoolDemo { static ExecutorService executorService = Executors.newSingleThreadExecutor(); public static void main(String[] args) throws Exception { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("產品經理規劃新需求"); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println("開發人員開發新需求功能"); } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { System.out.println("測試人員測試新功能"); } }); System.out.println("早上:"); System.out.println("產品經理來上班了"); System.out.println("測試人員來上班了"); System.out.println("開發人員來上班了"); System.out.println("領導吩咐:"); System.out.println("首先,產品經理規劃新需求..."); executorService.submit(thread1); System.out.println("然後,開發人員開發新需求功能..."); executorService.submit(thread2); System.out.println("最後,測試人員測試新功能..."); executorService.submit(thread3); executorService.shutdown(); } }
運行結果
早上:
產品經理來上班了
測試人員來上班了
開發人員來上班了
領導吩咐:
首先,產品經理規劃新需求...
然後,開發人員開發新需求功能...
最後,測試人員測試新功能...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
5.使用線程的Condition(條件變數)方法
Condition(條件變數):通常與一個鎖關聯。需要在多個Contidion中共用一個鎖時,可以傳遞一個Lock/RLock實例給構造方法,否則它將自己生成一個RLock實例。
-
Condition中await()方法類似於Object類中的wait()方法。
-
Condition中await(long time,TimeUnit unit)方法類似於Object類中的wait(long time)方法。
-
Condition中signal()方法類似於Object類中的notify()方法。
-
Condition中signalAll()方法類似於Object類中的notifyAll()方法。
應用場景:Condition是一個多線程間協調通信的工具類,使得某個,或者某些線程一起等待某個條件(Condition),只有當該條件具備( signal 或者 signalAll方法被帶調用)時 ,這些等待線程才會被喚醒,從而重新爭奪鎖。
package com.wwj.javabase.thread.order; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author wwj * 使用Condition(條件變數)實現線程按順序運行 */ public class ThreadConditionDemo { private static Lock lock = new ReentrantLock(); private static Condition condition1 = lock.newCondition(); private static Condition condition2 = lock.newCondition(); /** * 為什麼要加這兩個標識狀態? * 如果沒有狀態標識,當t1已經運行完了t2才運行,t2在等待t1喚醒導致t2永遠處於等待狀態 */ private static Boolean t1Run = false; private static Boolean t2Run = false; public static void main(String[] args) { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { lock.lock(); System.out.println("產品經理規劃新需求"); t1Run = true; condition1.signal(); lock.unlock(); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { lock.lock(); try { if(!t1Run){ System.out.println("開發人員先休息會..."); condition1.await(); } System.out.println("開發人員開發新需求功能"); t2Run = true; condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } lock.unlock(); } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { lock.lock(); try { if(!t2Run){ System.out.println("測試人員先休息會..."); condition2.await(); } System.out.println("測試人員測試新功能"); lock.unlock(); } catch (InterruptedException e) { e.printStackTrace(); } } }); System.out.println("早上:"); System.out.println("測試人員來上班了..."); thread3.start(); System.out.println("產品經理來上班了..."); thread1.start(); System.out.println("開發人員來上班了..."); thread2.start(); } }
運行結果:這裡輸出會有很多種順序,主要是因為線程進入的順序,造成鎖住線程的順序不一致
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
測試人員先休息會...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
6.使用線程的CountDownLatch(倒計數)方法
CountDownLatch:位於java.util.concurrent包下,利用它可以實現類似計數器的功能。
應用場景:比如有一個任務C,它要等待其他任務A,B執行完畢之後才能執行,此時就可以利用CountDownLatch來實現這種功能了。
package com.wwj.javabase.thread.order; import java.util.concurrent.CountDownLatch; /** * @author wwj * 通過CountDownLatch(倒計數)使線程按順序執行 */ public class ThreadCountDownLatchDemo { /** * 用於判斷線程一是否執行,倒計時設置為1,執行後減1 */ private static CountDownLatch c1 = new CountDownLatch(1); /** * 用於判斷線程二是否執行,倒計時設置為1,執行後減1 */ private static CountDownLatch c2 = new CountDownLatch(1); public static void main(String[] args) { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("產品經理規劃新需求"); //對c1倒計時-1 c1.countDown(); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { //等待c1倒計時,計時為0則往下運行 c1.await(); System.out.println("開發人員開發新需求功能"); //對c2倒計時-1 c2.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { //等待c2倒計時,計時為0則往下運行 c2.await(); System.out.println("測試人員測試新功能"); } catch (InterruptedException e) { e.printStackTrace(); } } }); System.out.println("早上:"); System.out.println("測試人員來上班了..."); thread3.start(); System.out.println("產品經理來上班了..."); thread1.start(); System.out.println("開發人員來上班了..."); thread2.start(); } }
運行結果
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
7.使用CyclicBarrier(迴環柵欄)實現線程按順序運行
CyclicBarrier(迴環柵欄):通過它可以實現讓一組線程等待至某個狀態之後再全部同時執行。叫做迴環是因為當所有等待線程都被釋放以後,CyclicBarrier可以被重用。我們暫且把這個狀態就叫做barrier,當調用await()方法之後,線程就處於barrier了。
應用場景:公司組織春游,等待所有的員工到達集合地點才能出發,每個人到達後進入barrier狀態。都到達後,喚起大家一起出發去旅行。
package com.wwj.javabase.thread.order; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * @author wwj * 使用CyclicBarrier(迴環柵欄)實現線程按順序運行 */ public class CyclicBarrierDemo { static CyclicBarrier barrier1 = new CyclicBarrier(2); static CyclicBarrier barrier2 = new CyclicBarrier(2); public static void main(String[] args) { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("產品經理規劃新需求"); //放開柵欄1 barrier1.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { //放開柵欄1 barrier1.await(); System.out.println("開發人員開發新需求功能"); //放開柵欄2 barrier2.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }); final Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { //放開柵欄2 barrier2.await(); System.out.println("測試人員測試新功能"); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }); System.out.println("早上:"); System.out.println("測試人員來上班了..."); thread3.start(); System.out.println("產品經理來上班了..."); thread1.start(); System.out.println("開發人員來上班了..."); thread2.start(); } }
運行結果
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
8.使用Sephmore(信號量)實現線程按順序運行
Sephmore(信號量):Semaphore是一個計數信號量,從概念上將,Semaphore包含一組許可證,如果有需要的話,每個acquire()方法都會阻塞,直到獲取一個可用的許可證,每個release()方法都會釋放持有許可證的線程,並且歸還Semaphore一個可用的許可證。然而,實際上並沒有真實的許可證對象供線程使用,Semaphore只是對可用的數量進行管理維護。
acquire():當前線程嘗試去阻塞的獲取1個許可證,此過程是阻塞的,當前線程獲取了1個可用的許可證,則會停止等待,繼續執行。
release():當前線程釋放1個可用的許可證。
應用場景:Semaphore可以用來做流量分流,特別是對公共資源有限的場景,比如資料庫連接。假設有這個的需求,讀取幾萬個文件的數據到資料庫中,由於文件讀取是IO密集型任務,可以啟動幾十個線程併發讀取,但是資料庫連接數只有10個,這時就必須控制最多只有10個線程能夠拿到資料庫連接進行操作。這個時候,就可以使用Semaphore做流量控制。
package com.wwj.javabase.thread.order; import java.util.concurrent.Semaphore; /** * @author wwj * 使用Sephmore(信號量)實現線程按順序運行 */ public class SemaphoreDemo { private static Semaphore semaphore1 = new Semaphore(1); private static Semaphore semaphore2 = new Semaphore(1); public static void main(String[] args) { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("產品經理規劃新需求"); semaphore1.release(); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { semaphore1.acquire(); System.out.println("開發人員開發新需求功能"); semaphore2.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { semaphore2.acquire(); thread2.join(); semaphore2.release(); System.out.println("測試人員測試新功能"); } catch (InterruptedException e) { e.printStackTrace(); } } }); System.out.println("早上:"); System.out.println("測試人員來上班了..."); thread3.start(); System.out.println("產品經理來上班了..."); thread1.start(); System.out.println("開發人員來上班了..."); thread2.start(); } }
運行結果
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
總結
看完了這麼多種方法,是不是對多線程有了更深入的瞭解呢?不妨自己試試吧(代碼拷貝均可運行)
使用的場景還有很多,根據開發需求場景,選擇合適的方法,達到事半功倍的效果。
本文歡迎各位轉載,但是轉載文章之後必須在文章開頭給出原文鏈接。感謝您的閱讀,如果您覺得閱讀本文對您有幫助,請點個“推薦”支持一下。