文章插圖

文章插圖
盡管通常每個子線程只需要完成自己的任務,但是有時我們可能希望多個線程一起完成一個任務,這涉及線程間的通信 。
該方法和本文中涉及的類是:thread.join(),object.wait(),object.notify(),CountdownLatch,CyclicBarrier,FutureTask,Callable等 。
這是本文涵蓋的代碼
我將使用幾個示例來說明如何在Java中實現線程間通信 。
如何使兩個線程按順序執行?如何使兩個線程以指定的方式有序相交?有四個線程:A,B,C和D(在A,B和C都完成執行并且A,B和C必須同步執行之前,不會執行D) 。三名運動員準備分開,然后在他們各自準備就緒后同時開始跑步 。子線程完成任務后,它將結果返回給主線程 。如何使兩個線程按順序執行?
假設有兩個線程:線程A和線程B 。兩個線程都可以依次打印三個數字(1-3) 。讓我們看一下代碼:
private static void demo1() {Thread A = new Thread(new Runnable() {@Overridepublic void run() {printNumber("A");}});Thread B = new Thread(new Runnable() {@Overridepublic void run() {printNumber("B");}});A.start();B.start();}的實現printNumber(String)如下,用于依次打印1、2和3這三個數字:private static void printNumber(String threadName) {int i=0;while (i++ < 3) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(threadName + "print:" + i);}}我們得到的結果是:B print: 1A print: 1B print: 2A print: 2B print: 3A print: 3您可以看到A和B同時打印數字 。那么,如果我們希望B在A打印完之后開始打印呢?我們可以使用該thread.join()方法,代碼如下:
private static void demo2() {Thread A = new Thread(new Runnable() {@Overridepublic void run() {printNumber("A");}});Thread B = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("B starts waiting for A");try {A.join();} catch (InterruptedException e) {e.printStackTrace();}printNumber("B");}});B.start();A.start();}現在獲得的結果是:B starts waiting for AA print: 1A print: 2A print: 3 B print: 1B print: 2B print: 3因此,我們可以看到該A.join()方法將使B等待直到A完成打印 。如何使兩個線程以指定的方式有序地相交?
那么,如果現在我們希望B在A打印完1之后立即開始打印1,2,3,然后A繼續打印2,3呢?顯然,我們需要更多細粒度地鎖來控制執行順序 。
在這里,我們可以利用object.wait()和object.notify()方法的優勢 。代碼如下:
/** * A 1, B 1, B 2, B 3, A 2, A 3 */private static void demo3() {Object lock = new Object();Thread A = new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock) {System.out.println("A 1");try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A 2");System.out.println("A 3");}}});Thread B = new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock) {System.out.println("B 1");System.out.println("B 2");System.out.println("B 3");lock.notify();}}});A.start();B.start();}結果如下:A 1A waiting… B 1B 2B 3A 2A 3那就是我們想要的 。怎么了?我將日志添加到上面的代碼中,以使其更易于理解 。
首先,我們創建一個由A和B共享的對象鎖: lock = new Object();當A得到鎖時,它首先打印1,然后調用lock.wait()使它進入等待狀態的方法,然后移交對鎖的控制 。在A調用lock.wait()釋放控制的方法并且B獲得鎖之前,B將不會執行 。B得到鎖后打印1、2、3,然后調用該lock.notify()方法喚醒正在等待的A;喚醒后,A將繼續打印其余的2、3 。
private static void demo3() {Object lock = new Object();Thread A = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("INFO: A is waiting for the lock");synchronized (lock) {System.out.println("INFO: A got the lock");System.out.println("A 1");try {System.out.println("INFO: A is ready to enter the wait state, giving up control of the lock");lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("INFO: B wakes up A, and A regains the lock");System.out.println("A 2");System.out.println("A 3");}}});Thread B = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("INFO: B is waiting for the lock");synchronized (lock) {System.out.println("INFO: B got the lock");System.out.println("B 1");System.out.println("B 2");System.out.println("B 3");System.out.println("INFO: B ends printing, and calling the notify method");lock.notify();}}});A.start();B.start();結果如下:INFO: A is waiting for the lockINFO: A got the lockA 1INFO: A is ready to enter the wait state, giving up control of the lockINFO: B is waiting for the lockINFO: B got the lockB 1B 2B 3INFO: B ends printing, and calling the notify methodINFO: B wakes up A, and A regains the lockA 2A 3在A,B和C都完成同步執行之后執行Dthread.join()前面介紹的方法允許一個線程在等待另一個線程完成運行之后繼續執行 。但是,如果我們將A,B和C順序連接到D線程中,它將使A,B和C依次執行,而我們希望它們三個同步運行 。
我們要達到的目標是:三個線程A,B和C可以同時開始運行,并且每個線程在獨立運行完成后都將通知D 。在A,B和C全部完成運行之后,D才會開始運行 。因此,我們用于CountdownLatch實現這種類型的通信 。其基本用法是:
創建一個計數器,并設置一個初始值, CountdownLatch countDownLatch = new CountDownLatch(3;countDownLatch.await()在等待線程中調用該方法,并進入等待狀態,直到計數值變為0為止;否則,進入等待狀態 。countDownLatch.countDown()在其他線程中調用該方法,該方法會將計數值減少一;當countDown()其他線程中的方法將計數值設為0時,countDownLatch.await()等待線程中的方法將立即退出并繼續執行以下代碼 。
實現代碼如下:
private static void runDAfterABC() {int worker = 3;CountDownLatch countDownLatch = new CountDownLatch(worker);new Thread(new Runnable() {@Overridepublic void run() {System.out.println("D is waiting for other three threads");try {countDownLatch.await();System.out.println("All done, D starts working");} catch (InterruptedException e) {e.printStackTrace();}}}).start();for (char threadName='A'; threadName <= 'C'; threadName++) {final String tN = String.valueOf(threadName);new Thread(new Runnable() {@Overridepublic void run() {System.out.println(tN + "is working");try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}System.out.println(tN + "finished");countDownLatch.countDown();}}).start();}}結果如下:D is waiting for other three threadsA is workingB is workingC is working A finishedC finishedB finishedAll done, D starts working實際上,CountDownLatch它本身是一個倒數計數器,我們將初始計數值設置為3 。運行D時,它首先調用該countDownLatch.await()方法以檢查計數器值是否為0,如果該值不為0,它將保持等待狀態 。。A,B和C分別countDownLatch.countDown()完成獨立運行后,將使用該方法將倒數計數器遞減1 。當它們全部三個完成運行時,計數器將減少為0;否則,計數器將減少為0 。然后,await()將觸發D的方法以結束A,B和C,并且D將開始繼續執行 。因此,CountDownLatch適用于一個線程需要等待多個線程的情況 。
3名跑步者準備跑步
三個跑步者準備分開,然后在每個人準備就緒后同時開始跑步 。
這次,三個線程A,B和C中的每個線程都需要分別進行準備,然后在三個線程全部準備好之后就開始同時運行 。我們應該如何實現呢?
在CountDownLatch上面可以用來計數下降,但完成計數的時候,只有一個線程的一個await()方法會得到響應,所以多線程不能在同一時間被觸發 。
為了達到線程互相等待的效果,我們可以使用CyclicBarrier數據結構,其基本用法是:
首先創建一個公共對象CyclicBarrier,并設置同時等待的線程數,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);這些線程開始同時進行準備 。準備好之后,他們需要等待其他人完成準備工作,因此調用該cyclicBarrier.await()方法來等待其他人;當需要同時等待的指定線程全部調用該cyclicBarrier.await()方法時,這意味著這些線程已準備就緒,那么這些線程將開始繼續同時執行 。
實現代碼如下 。想象一下,有三位跑步者需要同時開始跑步,因此他們需要等待其他跑步者,直到所有人都準備就緒為止 。
private static void runABCWhenAllReady() {int runner = 3;CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);final Random random = new Random();for (char runnerName='A'; runnerName <= 'C'; runnerName++) {final String rN = String.valueOf(runnerName);new Thread(new Runnable() {@Overridepublic void run() {long prepareTime = random.nextInt(10000) + 100;System.out.println(rN + "is preparing for time:" + prepareTime);try {Thread.sleep(prepareTime);} catch (Exception e) {e.printStackTrace();}try {System.out.println(rN + "is prepared, waiting for others");cyclicBarrier.await(); // The current runner is ready, waiting for others to be ready} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}System.out.println(rN + "starts running"); // All the runners are ready to start running together}}).start();}}結果如下:A is preparing for time: 4131B is preparing for time: 6349C is preparing for time: 8206 A is prepared, waiting for others B is prepared, waiting for others C is prepared, waiting for others C starts runningA starts runningB starts running子線程將結果返回到主線程在實際開發中,通常我們需要創建子線程來執行一些耗時的任務,然后將執行結果傳遞回主線程 。那么如何在Java中實現呢?
因此,通常,在創建線程時,我們會將Runnable對象傳遞給Thread以便執行 。Runnable的定義如下:
public interface Runnable {public abstract void run();}您可以看到該run()方法執行后不返回任何結果 。如果要返回結果怎么辦?在這里,您可以使用另一個類似的接口類Callable:@FunctionalInterfacepublic interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;}可以看出,最大的區別Callable在于它返回了泛型 。因此,下一個問題是,如何將子線程的結果傳遞回去?Java有一個類,FutureTask可以與一起使用Callable,但是請注意,get用于獲取結果的方法將阻塞主線程 。
例如,我們希望子線程計算從1到100的總和,然后將結果返回給主線程 。
private static void doTaskWithResultInWorker() {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {System.out.println("Task starts");Thread.sleep(1000);int result = 0;for (int i=0; i<=100; i++) {result += i;}System.out.println("Task finished and return result");return result;}};FutureTask<Integer> futureTask = new FutureTask<>(callable);new Thread(futureTask).start();try {System.out.println("Before futureTask.get()");System.out.println("Result:" + futureTask.get());System.out.println("After futureTask.get()");} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}結果如下:Before futureTask.get() Task startsTask finished and return result Result: 5050After futureTask.get()可以看出,當主線程調用該futureTask.get()方法時,它將阻塞主線程 。然后Callable開始在內部執行并返回操作結果;然后futureTask.get()獲取結果,主線程恢復運行 。在這里,我們可以了解到FutureTask和Callable可以直接在主線程中獲得子線程的結果,但是它們會阻塞主線程 。當然,如果您不想阻塞主線程,可以考慮使用ExecutorService將FutureTask線程放入線程池來管理執行 。
概括
【線程通信的三種方式 線程間是如何通信的】多線程是現代語言的常見功能,線程間通信,線程同步和線程安全是非常重要的主題 。
- 簡單介紹MD5加密算法 md5是常用的數據加密
- 韭菜炒蘑菇的家常做法
- 調理氣血的中藥副作用
- 行程碼是否帶星 行程碼帶有星號的屬于重點人群嗎
- eddy短彈簧哪里生產的 eddy短彈簧質量和舒適性怎樣
- 糖對牙齒的危害
- 4g手機哪年上市時間 4g手機是哪一年上市的
- excel隱藏的表格怎么恢復 excel表格隱藏表格怎么恢復
- 移動手機價格 中國移動的手機多少錢
- 挽回男友的短信,直擊內心效果極佳
