【多線程併發編程】十 線程的join

程序猿學社的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線程,才能繼續運行。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章