Java?Thread?join()的使用場景和原理詳解
1、使用場景
一般情況下,主線程創(chuàng)建并啟動(dòng)子線程,如果子線程中執(zhí)行大量耗時(shí)運(yùn)算,主線程可能早于子線程結(jié)束。如果主線程需要知道子線程的執(zhí)行結(jié)果時(shí),就需要等待子線程執(zhí)行結(jié)束。主線程可以sleep(xx),但這樣的xx時(shí)間不好確定,因?yàn)樽泳€程的執(zhí)行時(shí)間不確定,join()方法比較合適這個(gè)場景。
2、join()方法定義和使用
join()是Thread類的一個(gè)方法。根據(jù)jdk文檔的定義:
public final void join()throws InterruptedException: Waits for this thread to die.
join()方法的作用,是等待這個(gè)線程結(jié)束;這個(gè)定義比較模糊,個(gè)人認(rèn)為"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()方法,此時(shí)主線程需要等待(阻塞),等待子線程結(jié)束了(Waits for this thread to die.),才能繼續(xù)執(zhí)行t.join()之后的代碼塊。

來看一個(gè)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)部的一個(gè) native 方法,只要當(dāng)線程處于 NEW 和 TERMINATED 狀態(tài)時(shí)返回false,其余都返回true。join() 其實(shí)就是通過調(diào)用 wait() 方法(Object定義的,詳見線程間通信),wait(0) 表示當(dāng)前線程立即釋放鎖(這里的鎖指的就是前一個(gè)線程)并進(jìn)入 waiting 狀態(tài),等待其他線程喚醒(notify/notifyAll)。
join() 一共有三個(gè)重載版本,分別是無參、一個(gè)參數(shù)、兩個(gè)參數(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) 三個(gè)方法都被final修飾,無法被子類重寫。
(2) join(long), join(long, long) 是synchronized method,同步的對(duì)象是當(dāng)前線程實(shí)例。
(3) join() 和 join(0) 是等價(jià)的,表示一直等下去;join(非0)表示等待一段時(shí)間。
從源碼可以看到 join(0) 調(diào)用了Object.wait(0),其中Object.wait(0) 會(huì)一直等待,直到被notify/中斷才返回。
while(isAlive())是為了防止子線程偽喚醒(spurious wakeup),只要子線程沒有TERMINATED的,父線程就需要繼續(xù)等下去。
(4) join() 和 sleep() 一樣,可以被中斷(被中斷時(shí),會(huì)拋出 InterrupptedException 異常);不同的是,join() 內(nèi)部調(diào)用了 wait(),會(huì)出讓鎖,而 sleep() 會(huì)一直保持鎖。
4、join() 方法注意點(diǎn)
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)用才有意義。這個(gè)也很容易理解:線程沒有執(zhí)行,isAlive() 方法就不會(huì)返回true,也就不會(huì)執(zhí)行 wait(0) 方法了。
4.2、join() 與中斷異常
在join()過程中,如果當(dāng)前線程被中斷,則當(dāng)前線程出現(xiàn)異常。(注意是調(diào)用thread.join()的線程被中斷才會(huì)進(jìn)入異常,比如a線程調(diào)用b.join(),a中斷會(huì)報(bào)異常而b中斷不會(huì)異常)
下面總結(jié)下中斷與 wait()/join()/sleep() 的使用:
調(diào)用interrupt()方法,立刻改變的是中斷狀態(tài),但如果不是在阻塞態(tài),就不會(huì)拋出異常;如果在進(jìn)入阻塞態(tài)后,中斷狀態(tài)為已中斷,就會(huì)立刻拋出異常,但同時(shí)中斷標(biāo)志也會(huì)被系統(tǒng)復(fù)位。
(1)sleep() &interrupt()
線程A正在使用sleep()暫停著: Thread.sleep(100000),如果要取消它的等待狀態(tài),可以在正在執(zhí)行的線程里(比如這里是B)調(diào)用a.interrupt()[a是線程A對(duì)應(yīng)到的Thread實(shí)例],令線程A放棄睡眠操作。即,在線程B中執(zhí)行a.interrupt(),處于阻塞中的線程a將放棄睡眠操作。
當(dāng)在sleep中時(shí)線程被調(diào)用interrupt()時(shí),就馬上會(huì)放棄暫停的狀態(tài)并拋出InterruptedException。拋出異常的,是A線程。
(2)wait() &interrupt()
線程A調(diào)用了wait()進(jìn)入了等待狀態(tài),也可以用interrupt()取消。不過這時(shí)候要注意鎖定的問題。線程在進(jìn)入等待區(qū),會(huì)把鎖定解除,當(dāng)對(duì)等待中的線程調(diào)用interrupt()時(shí),會(huì)先重新獲取鎖定,再拋出異常。在獲取鎖定之前,是無法拋出異常的。
(3)join() &interrupt()
當(dāng)線程以join()等待其他線程結(jié)束時(shí),當(dāng)它被調(diào)用interrupt(),它與sleep()時(shí)一樣,會(huì)馬上跳到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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot項(xiàng)目刪除Bean或者不加載Bean的問題解決
文章介紹了在Spring Boot項(xiàng)目中如何使用@ComponentScan注解和自定義過濾器實(shí)現(xiàn)不加載某些Bean的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2025-01-01
Java8 stream 中利用 groupingBy 進(jìn)行多字段分組求和案例
這篇文章主要介紹了Java8 stream 中利用 groupingBy 進(jìn)行多字段分組求和案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Java中ReentrantLock和ReentrantReadWriteLock的原理
這篇文章主要介紹了Java中ReentrantLock和ReentrantReadWriteLock的原理,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-09-09
Java實(shí)現(xiàn)數(shù)組反轉(zhuǎn)翻轉(zhuǎn)的方法實(shí)例
本篇文章主要介紹了Java實(shí)現(xiàn)數(shù)組反轉(zhuǎn)翻轉(zhuǎn)的方法實(shí)例,詳細(xì)的介紹了3種實(shí)現(xiàn)方法,有興趣的可以了解一下。2017-04-04
一文了解Java讀寫鎖ReentrantReadWriteLock的使用
ReentrantReadWriteLock稱為讀寫鎖,它提供一個(gè)讀鎖,支持多個(gè)線程共享同一把鎖。這篇文章主要講解一下ReentrantReadWriteLock的使用和應(yīng)用場景,感興趣的可以了解一下2022-10-10
使用Spring從YAML文件讀取內(nèi)容映射為Map方式
這篇文章主要介紹了使用Spring從YAML文件讀取內(nèi)容映射為Map方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02

