程序猿學社的GitHub,歡迎Star
https://github.com/ITfqyd/cxyxs
本文已記錄到github,形成對應專題。
前言
學過數據庫的社友,應該都使用過join,當然,本文講的join,是線程的join方法。
結論
先給出結論,join會堵塞當然調用的線程,直到拿到結果後,纔會繼續執行當前調用的那個線程。
- 回想起,19年的一個大冬天,凌晨2點,高鐵轉火車,火車還晚點2小個小時,坐過火車的人,應該都知道,火車的人數是真的多,所以,我早早的開始排隊,大約從第50個,變成100,200,300。join可以理解爲,就是這些插隊的人,我很急,急着回家。當然,插隊的行爲,真的十分的不文明。
場景
有個別社友就在問,這個學了以後,能否派上用處,對此,我只能說,這個社友還是蠻務實的,實際上,很多的東西,不一定需要派上用場,纔去學習他,就是爲了擴充我們的視野,把我們的基礎打牢固。我一直堅信的一個理念,存在便有一定的道理。
- 有不少做過導出的朋友,可以發現,有時候大數據量導出很慢,又不少朋友在想辦法看看能否縮短這個時間。這裏我們就可以使用多線程導出,通過join獲取結果,再把結果合併,例如一頁10w條,你查下出來需要20s,你設計成10頁,起10個線程,是不是1次只用查詢1w條,是不是大大的節約了時間。
- 一般需要等子線程j的運行結果後,主線程才繼續運行的場景,我們都可以使用join。
實戰
不加join代碼
package com.cxyxs.thread.nine;
/**
* Description:轉發請註明來源 程序猿學社 - https://ithub.blog.csdn.net/
* Author: 程序猿學社
* Date: 2020/2/28 10:21
* Modified By:
*/
public class JoinThread implements Runnable{
@Override
public void run() {
System.out.println("子線程在運行中!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子線程已運行完!");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new JoinThread());
thread.start();
//thread.join();
Thread.sleep(100);
System.out.println("main方法已運行完!");
}
}
不適用join大家猜猜,輸出到控制檯的是什麼結果?
- sleep休眠100毫秒,也是爲了保證子線程先運行。不然,這個結果很難確定誰先,誰後。
- 首先main方法運行,main方法休眠100ms,子線程就搶到時間片,打印一句話,子線程休眠1秒,main方法搶到時間片,繼續運行。
增加join代碼
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new JoinThread());
thread.start();
thread.join();
Thread.sleep(100);
System.out.println("main方法已運行完!");
}
增加一個join,大家覺得,他的輸出結果應該是什麼(JoinThread類的代碼省略點)?
- 我們可以發現,先運行完後子線程的所有業務後,纔開始運行main後面的邏輯代碼。所以join,我們可以理解爲插隊加塞。在main線程裏面調用,就會堵塞main線程。一直到返回結果後,才繼續運行main線程。
看到這裏,又有社友提出一個疑問,會不會堵塞其他的子線程。我們再增加一個子線程試試。
增加join代碼,模擬兩個子線程
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("t1線程正在工作中");
});
Thread thread = new Thread(new JoinThread());
thread.start();
//這句代碼很關鍵
thread.join();
t1.start();
Thread.sleep(100);
System.out.println("main方法已運行完!");
}
出現這種結果後,很多社友紛紛給我說,社長,臉痛不,這打臉也太快了把。
爲什麼會出現這種結果?
- thread線程啓動後,就直接調用join方法,很明顯會堵塞main線程,t1都沒有啓動,怎麼去cpu中搶時間片,是不是很多社友,都覺得社長在狡辯。沒關係,我們把join的位置變動一下,奇蹟的事情,就會發生了,各位社友,睜大眼睛,好好看。
模擬兩個子線程,移動join位置
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("t1線程正在工作中");
});
Thread thread = new Thread(new JoinThread());
thread.start();
t1.start();
//這句代碼很關鍵
thread.join();
Thread.sleep(100);
System.out.println("main方法已運行完!");
}
- 通過上圖的測試結果,我們可以發現,堵塞的只是當前線程,也就是我們的main線程(調用方),依次,我們可以下這個結論,誰調用誰堵塞,不會影響其他的線程正常的運行。是不會影響其他的子線程正常的運行的。這也是不少人存在的一個誤區。
源碼分析
通過上面的demo,我們對join方法的使用,有了一個大致的認識。但是,他底層的代碼怎麼一回事,話不多說,上源碼。
我們大部分的時候,使用的都是不帶參數的調用join
public final void join() throws InterruptedException {
join(0);
}
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;
}
}
}
- 直接調用join方法,不指定參數,默認就是0,0標識沒有設置超時時間會一直等待。
- 大於0,會判斷線程,在規定的時間內,是否執行完,沒有執行完,也不會等待。
- isAlive方法,判斷線程是否活着。
- join方法實際上就是調用wait方法實現線程的堵塞。知道調用notify纔會被喚醒。
- join函數用了synchronized關鍵字,表示線程安全。
很多的社友,很困惑,在mian方法中調用另外一個線程的join方法,爲什麼,main線程會進入等待狀態,直到另外一個線程運行完後,纔開始運行。
通過查看源碼,我們可以知道,調用join,實際上就是調用wait,我們都知道wait是與synchronized成對存在的。也就說讓持有這個鎖的線程進入等待隊列中,那是誰進入等待隊列?
查看join的源碼,我們可以發現他就是一個同步代碼塊,main線程調用join。所有main線程會進入等待隊列中 。
main線程爲什麼等待一段時間後,又可以繼續運行?
這是因爲,再main線程中,調用的另外一個線程,結束以後,會調用notifyAll方法,從而我們的main線程,才能繼續運行。