[转载] --- 让线程按顺序执行8种方法
看到一篇比較用心的總結,涉及到很多知識點,轉來保存,而且我把里面的每個方法都試了一遍,親測沒問題
此次轉載,還新增了一些說明和結構
我的總結:
其實,讓線程按順序執行,其實就是一個讓多線程強行串行化的過程。使用的場景比較少,但對于學習這些知識點,確實是一個好的切入口,通過這個可以清晰的感覺到,線程直接的互相等待,互相制約。
對于我來說,平時使用CountDownLatch的次數最多。
一、前言
本文使用了8種方法實現在多線程中讓線程按順序運行的方法,涉及到多線程中許多常用的方法,不止為了知道如何讓線程按順序運行,更是讓讀者對多線程的使用有更深刻的了解。
使用的方法如下:
1.1、使用線程的join方法
當一個線程必須等待另一個線程執行完畢才能執行時可以使用join方法。
1.2、使用主線程的join方法
在父進程調用子進程的join()方法后,父進程需要等待子進程運行完再繼續運行。
1.3、使用線程的wait方法
Java實現生產者消費者的方式。
1.4、使用線程的線程池方法
線程池的相關解釋可以看我這篇文章
串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
1.5、使用線程的Condition(條件變量)方法
Condition是一個多線程間協調通信的工具類,使得某個,或者某些線程一起等待某個條件(Condition),只有當該條件具備( signal 或者 signalAll方法被帶調用)時 ,這些等待線程才會被喚醒,從而重新爭奪鎖。
1.6、使用線程的CountDownLatch(倒計數)方法
比如有一個任務C,它要等待其他任務A,B執行完畢之后才能執行,此時就可以利用CountDownLatch來實現這種功能了。
1.7、使用線程的CyclicBarrier(回環柵欄)方法
公司組織春游,等待所有的員工到達集合地點才能出發,每個人到達后進入barrier狀態。都到達后,喚起大家一起出發去旅行。
1.8、使用線程的Semaphore(信號量)方法
Semaphore可以用來做流量分流,特別是對公共資源有限的場景,比如數據庫連接。假設有這個的需求,讀取幾萬個文件的數據到數據庫中,由于文件讀取是IO密集型任務,可以啟動幾十個線程并發讀取,但是數據庫連接數只有10個,這時就必須控制最多只有10個線程能夠拿到數據庫連接進行操作。這個時候,就可以使用Semaphore做流量控制。
二、實現
我們下面需要完成這樣一個應用場景:
1.早上;2.測試人員、產品經理、開發人員陸續的來公司上班;3.產品經理規劃新需求;4.開發人員開發新需求功能;5.測試人員測試新功能。
規劃需求,開發需求新功能,測試新功能是一個有順序的,我們把thread1看做產品經理,thread2看做開發人員,thread3看做測試人員。
2.1 使用線程的join方法
join():是Theard的方法,作用是調用線程需等待該join()線程執行完成后,才能繼續用下運行。
應用場景:當一個線程必須等待另一個線程執行完畢才能執行時可以使用join方法。
package com.線程;/*** @Auther: curry.zhang* @Date: 2019/9/23 16:04* @Description: 通過子程序join使線程按順序執行*/ public class ThreadJoinDemo {public static void main(String[] args) {final Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("產品經理規劃新需求");}});final Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {try {thread1.join();System.out.println("開發人員開發新需求功能");} catch (InterruptedException e) {e.printStackTrace();}}});Thread thread3 = new Thread(new Runnable() {@Overridepublic 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.2、使用主線程的join方法
這里是在主線程中使用join()來實現對線程的阻塞。
在父進程調用子進程的join()方法后,父進程需要等待子進程運行完再繼續運行。
package com.線程;/*** @Auther: curry.zhang* @Date: 2019/9/23 16:43* @Description: 通過主程序join使線程按順序執行*/ public class ThreadMainJoinDemo {public static void main(String[] args) throws Exception {final Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("產品經理正在規劃新需求...");}});final Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("開發人員開發新需求功能");}});final Thread thread3 = new Thread(new Runnable() {@Overridepublic 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();} }運行結果:
早上: 產品經理來上班了 測試人員來上班了 開發人員來上班了 開發人員和測試人員休息會... 產品經理正在規劃新需求... 產品經理新需求規劃完成! 測試人員休息會... 開發人員開發新需求功能 測試人員測試新功能2.3、使用線程的wait方法
wait():是Object的方法,作用是讓當前線程進入等待狀態,同時,wait()也會讓當前線程釋放它所持有的鎖。“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)
notify()和notifyAll():是Object的方法,作用則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒所有的線程。
wait(long timeout):讓當前線程處于“等待(阻塞)狀態”,“直到其他線程調用此對象的notify()方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
應用場景:Java實現生產者消費者的方式。
package com.線程;/*** @Auther: curry.zhang* @Date: 2019/9/23 16:48* @Description:*/ 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() {@Overridepublic void run() {synchronized (myLock1){System.out.println("產品經理規劃新需求...");t1Run = true;myLock1.notify();}}});final Thread thread2 = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic 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();} }運行結果:
早上: 測試人員來上班了... 產品經理來上班了... 開發人員來上班了... 產品經理規劃新需求... 測試人員先休息會... 開發人員開發新需求功能 測試人員測試新功能2.4、使用線程的線程池方法
JAVA通過Executors提供了四種線程池
單線程化線程池(newSingleThreadExecutor);
可控最大并發數線程池(newFixedThreadPool);
可回收緩存線程池(newCachedThreadPool);
支持定時與周期性任務的線程池(newScheduledThreadPool)。
單線程化線程池(newSingleThreadExecutor):優點,串行執行所有任務。
submit():提交任務。
shutdown():方法用來關閉線程池,拒絕新任務。
應用場景:串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
package com.線程;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;/*** @Auther: curry.zhang* @Date: 2019/9/23 16:52* @Description: 通過SingleThreadExecutor讓線程按順序執行*/ public class ThreadPoolDemo {static ExecutorService executorService = Executors.newSingleThreadExecutor();public static void main(String[] args) throws Exception {final Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("產品經理規劃新需求");}});final Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("開發人員開發新需求功能");}});Thread thread3 = new Thread(new Runnable() {@Overridepublic 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();} }運行結果:
早上: 產品經理來上班了 測試人員來上班了 開發人員來上班了 領導吩咐: 首先,產品經理規劃新需求... 然后,開發人員開發新需求功能... 最后,測試人員測試新功能... 產品經理規劃新需求 開發人員開發新需求功能 測試人員測試新功能2.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.線程;import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;/*** @Auther: curry.zhang* @Date: 2019/9/23 17:02* @Description: 使用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() {@Overridepublic void run() {lock.lock();System.out.println("產品經理規劃新需求");t1Run = true;condition1.signal();lock.unlock();}});final Thread thread2 = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic 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();} }運行結果:
早上: 測試人員來上班了... 產品經理來上班了... 開發人員來上班了... 測試人員先休息會... 產品經理規劃新需求 開發人員開發新需求功能 測試人員測試新功能2.6、使用線程的CountDownLatch(倒計數)方法
CountDownLatch:位于java.util.concurrent包下,利用它可以實現類似計數器的功能。
應用場景:比如有一個任務C,它要等待其他任務A,B執行完畢之后才能執行,此時就可以利用CountDownLatch來實現這種功能了。
package com.線程;import java.util.concurrent.CountDownLatch;/*** @Auther: curry.zhang* @Date: 2019/9/23 17:05* @Description:*/ 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() {@Overridepublic void run() {System.out.println("產品經理規劃新需求");//對c1倒計時-1c1.countDown();}});final Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {try {//等待c1倒計時,計時為0則往下運行c1.await();System.out.println("開發人員開發新需求功能");//對c2倒計時-1c2.countDown();} catch (InterruptedException e) {e.printStackTrace();}}});Thread thread3 = new Thread(new Runnable() {@Overridepublic 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();} }運行結果:
早上: 測試人員來上班了... 產品經理來上班了... 開發人員來上班了... 產品經理規劃新需求 開發人員開發新需求功能 測試人員測試新功能2.7、使用CyclicBarrier(回環柵欄)實現線程按順序運行
CyclicBarrier(回環柵欄):通過它可以實現讓一組線程等待至某個狀態之后再全部同時執行。叫做回環是因為當所有等待線程都被釋放以后,CyclicBarrier可以被重用。我們暫且把這個狀態就叫做barrier,當調用await()方法之后,線程就處于barrier了。
應用場景:公司組織春游,等待所有的員工到達集合地點才能出發,每個人到達后進入barrier狀態。都到達后,喚起大家一起出發去旅行。
package com.線程;import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier;/*** @Auther: curry.zhang* @Date: 2019/9/23 17:16* @Description: 使用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() {@Overridepublic void run() {try {System.out.println("產品經理規劃新需求");//放開柵欄1barrier1.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}});final Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {try {//放開柵欄1barrier1.await();System.out.println("開發人員開發新需求功能");//放開柵欄2barrier2.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}});final Thread thread3 = new Thread(new Runnable() {@Overridepublic void run() {try {//放開柵欄2barrier2.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();} }運行結果:
早上: 測試人員來上班了... 產品經理來上班了... 開發人員來上班了... 產品經理規劃新需求 開發人員開發新需求功能 測試人員測試新功能2.8、使用Sephmore(信號量)實現線程按順序運行
Sephmore(信號量):Semaphore是一個計數信號量,從概念上將,Semaphore包含一組許可證,如果有需要的話,每個acquire()方法都會阻塞,直到獲取一個可用的許可證,每個release()方法都會釋放持有許可證的線程,并且歸還Semaphore一個可用的許可證。然而,實際上并沒有真實的許可證對象供線程使用,Semaphore只是對可用的數量進行管理維護。
acquire():當前線程嘗試去阻塞的獲取1個許可證,此過程是阻塞的,當前線程獲取了1個可用的許可證,則會停止等待,繼續執行。
release():當前線程釋放1個可用的許可證。
應用場景:Semaphore可以用來做流量分流,特別是對公共資源有限的場景,比如數據庫連接。假設有這個的需求,讀取幾萬個文件的數據到數據庫中,由于文件讀取是IO密集型任務,可以啟動幾十個線程并發讀取,但是數據庫連接數只有10個,這時就必須控制最多只有10個線程能夠拿到數據庫連接進行操作。這個時候,就可以使用Semaphore做流量控制。
package com.線程;import java.util.concurrent.Semaphore;/*** @Auther: curry.zhang* @Date: 2019/9/23 17:09* @Description: 使用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() {@Overridepublic void run() {System.out.println("產品經理規劃新需求");semaphore1.release();}});final Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {try {semaphore1.acquire();System.out.println("開發人員開發新需求功能");semaphore2.release();} catch (InterruptedException e) {e.printStackTrace();}}});Thread thread3 = new Thread(new Runnable() {@Overridepublic 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();} }運行結果:
早上: 測試人員來上班了... 產品經理來上班了... 開發人員來上班了... 產品經理規劃新需求 開發人員開發新需求功能 測試人員測試新功能總結
以上是生活随笔為你收集整理的[转载] --- 让线程按顺序执行8种方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: React Native新手引导
- 下一篇: rocketmq批量消费