Java?Thread?join()的使用場景和原理詳解
1、使用場景
一般情況下,主線程創(chuàng)建并啟動子線程,如果子線程中執(zhí)行大量耗時運算,主線程可能早于子線程結(jié)束。如果主線程需要知道子線程的執(zhí)行結(jié)果時,就需要等待子線程執(zhí)行結(jié)束。主線程可以sleep(xx),但這樣的xx時間不好確定,因為子線程的執(zhí)行時間不確定,join()方法比較合適這個場景。
2、join()方法定義和使用
join()是Thread類的一個方法。根據(jù)jdk文檔的定義:
public final void join()throws InterruptedException: Waits for this thread to die.
join()方法的作用,是等待這個線程結(jié)束;這個定義比較模糊,個人認為"Java 7 Concurrency Cookbook"的定義較為清晰:
Waiting for the finalization of a thread In some situations, we will have to wait for the finalization of a thread. For example, we may have a program that will begin initializing the resources it needs before proceeding with the rest of the execution. We can run the initialization tasks as threads and wait for its finalization before continuing with the rest of the program. For this purpose, we can use the join() method of the Thread class. When we call this method using a thread object, it suspends the execution of the calling thread until the object called finishes its execution.
解釋一下,是主線程等待子線程的終止。也就是說主線程的代碼塊中,如果碰到了t.join()方法,此時主線程需要等待(阻塞),等待子線程結(jié)束了(Waits for this thread to die.),才能繼續(xù)執(zhí)行t.join()之后的代碼塊。

來看一個join()的案例demo:
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
//獲取當(dāng)前線程信息
Thread previousThread= Thread.currentThread();
for(int i=0;i<10;i++){
Thread thread=new Thread(new Domino(previousThread));
thread.start();
previousThread=thread;
}
TimeUnit.SECONDS.sleep(5);
System.out.println("Thread.currentThread().getName()+\" terminate.\" = " + Thread.currentThread().getName()+" terminate.");
}
static class Domino implements Runnable{
private Thread thread;
public Domino(Thread thread){
this.thread=thread;
}
@Override
public void run() {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " terminate.");
}
}
}這段demo的邏輯就是當(dāng)前線程等待它前面的線程執(zhí)行結(jié)束再執(zhí)行,輸出如下:
Thread.currentThread().getName()+" terminate." = main terminate.
Thread-0 terminate
Thread-1 terminate
Thread-2 terminate
Thread-3 terminate
Thread-4 terminate
Thread-5 terminate
Thread-6 terminate
Thread-7 terminate
Thread-8 terminate
Thread-9 terminate
3、源碼分析
public final void join() throws InterruptedException {
join(0);
}再看join(long millis)方法:
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}isAlive() 是 Thread 內(nèi)部的一個 native 方法,只要當(dāng)線程處于 NEW 和 TERMINATED 狀態(tài)時返回false,其余都返回true。join() 其實就是通過調(diào)用 wait() 方法(Object定義的,詳見線程間通信),wait(0) 表示當(dāng)前線程立即釋放鎖(這里的鎖指的就是前一個線程)并進入 waiting 狀態(tài),等待其他線程喚醒(notify/notifyAll)。
join() 一共有三個重載版本,分別是無參、一個參數(shù)、兩個參數(shù):
public final void join() throws InterruptedException; public final synchronized void join(long millis) throws InterruptedException; public final synchronized void join(long millis, int nanos) throws InterruptedException;
(1) 三個方法都被final修飾,無法被子類重寫。
(2) join(long), join(long, long) 是synchronized method,同步的對象是當(dāng)前線程實例。
(3) join() 和 join(0) 是等價的,表示一直等下去;join(非0)表示等待一段時間。
從源碼可以看到 join(0) 調(diào)用了Object.wait(0),其中Object.wait(0) 會一直等待,直到被notify/中斷才返回。
while(isAlive())是為了防止子線程偽喚醒(spurious wakeup),只要子線程沒有TERMINATED的,父線程就需要繼續(xù)等下去。
(4) join() 和 sleep() 一樣,可以被中斷(被中斷時,會拋出 InterrupptedException 異常);不同的是,join() 內(nèi)部調(diào)用了 wait(),會出讓鎖,而 sleep() 會一直保持鎖。
4、join() 方法注意點
4.1、join() 與 start() 的調(diào)用順序
package com.dxz.join;
class BThread extends Thread {
public BThread() {
super("[BThread] Thread");
};
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
try {
for (int i = 0; i < 5; i++) {
System.out.println(threadName + " loop at " + i);
Thread.sleep(1000);
}
System.out.println(threadName + " end.");
} catch (Exception e) {
System.out.println("Exception from " + threadName + ".run");
}
}
}
public class TestDemo {
public static void main(String[] args) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
BThread bt = new BThread();
try {
bt.join();
bt.start();
Thread.sleep(2000);
} catch (Exception e) {
System.out.println("Exception from main");
}
System.out.println(threadName + " end!");
}
}執(zhí)行結(jié)果:
main start.
[BThread] Thread start.
[BThread] Thread loop at 0
[BThread] Thread loop at 1
main end!
[BThread] Thread loop at 2
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end.
main線程沒有等待 [BThread] 線程執(zhí)行完再執(zhí)行。join方法必須在線程start方法調(diào)用之后調(diào)用才有意義。這個也很容易理解:線程沒有執(zhí)行,isAlive() 方法就不會返回true,也就不會執(zhí)行 wait(0) 方法了。
4.2、join() 與中斷異常
在join()過程中,如果當(dāng)前線程被中斷,則當(dāng)前線程出現(xiàn)異常。(注意是調(diào)用thread.join()的線程被中斷才會進入異常,比如a線程調(diào)用b.join(),a中斷會報異常而b中斷不會異常)
下面總結(jié)下中斷與 wait()/join()/sleep() 的使用:
調(diào)用interrupt()方法,立刻改變的是中斷狀態(tài),但如果不是在阻塞態(tài),就不會拋出異常;如果在進入阻塞態(tài)后,中斷狀態(tài)為已中斷,就會立刻拋出異常,但同時中斷標(biāo)志也會被系統(tǒng)復(fù)位。
(1)sleep() &interrupt()
線程A正在使用sleep()暫停著: Thread.sleep(100000),如果要取消它的等待狀態(tài),可以在正在執(zhí)行的線程里(比如這里是B)調(diào)用a.interrupt()[a是線程A對應(yīng)到的Thread實例],令線程A放棄睡眠操作。即,在線程B中執(zhí)行a.interrupt(),處于阻塞中的線程a將放棄睡眠操作。
當(dāng)在sleep中時線程被調(diào)用interrupt()時,就馬上會放棄暫停的狀態(tài)并拋出InterruptedException。拋出異常的,是A線程。
(2)wait() &interrupt()
線程A調(diào)用了wait()進入了等待狀態(tài),也可以用interrupt()取消。不過這時候要注意鎖定的問題。線程在進入等待區(qū),會把鎖定解除,當(dāng)對等待中的線程調(diào)用interrupt()時,會先重新獲取鎖定,再拋出異常。在獲取鎖定之前,是無法拋出異常的。
(3)join() &interrupt()
當(dāng)線程以join()等待其他線程結(jié)束時,當(dāng)它被調(diào)用interrupt(),它與sleep()時一樣,會馬上跳到catch塊里.。
注意,調(diào)用的interrupt()方法,一定是調(diào)用被阻塞線程的interrupt方法。如在線程a中調(diào)用線程t.interrupt()。
總結(jié)
到此這篇關(guān)于Java Thread join()的使用場景和原理的文章就介紹到這了,更多相關(guān)Java Thread join()使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot過濾器執(zhí)行兩次的解決及跨域過濾器問題
這篇文章主要介紹了springboot過濾器執(zhí)行兩次的解決及跨域過濾器問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
Spring Security實現(xiàn)5次密碼錯誤觸發(fā)賬號自動鎖定功能
在現(xiàn)代互聯(lián)網(wǎng)應(yīng)用中,賬號安全是重中之重,然而,暴力 破解攻擊依然是最常見的安全威脅之一,攻擊者通過自動化腳本嘗試大量的用戶名和密碼組合,試圖找到漏洞進入系統(tǒng),所以為了解決這一問題,賬號鎖定機制被廣泛應(yīng)用,本文介紹了Spring Security實現(xiàn)5次密碼錯誤觸發(fā)賬號鎖定功能2024-12-12
SpringBoot+logback默認日志的配置和使用方式
這篇文章主要介紹了SpringBoot+logback默認日志的配置和使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05

