Java Thread.join()

t.join() / t.join(long millis),當(dāng)前線程里調(diào)用其它線程 t 的 join 方法国夜,當(dāng)前線程阻塞妈橄,但不釋放對象鎖涌哲,直到線程 t 執(zhí)行完畢或者 millis 時間到,當(dāng)前線程進(jìn)入可運行狀態(tài)剩燥。

t.join(); // 使調(diào)用線程 t 在此之前執(zhí)行完畢
t.join(1000); // 等待 t 線程立轧,等待時間是 1000 毫秒

JDK 源碼:

public final void join() throws InterruptedException {  
    join(0);  
}

// 成員方法加了 synchronized 說明是 synchronized(this) 
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");  
    }  
    // 這里使用了 while 循環(huán)做判斷,然后調(diào)用 wait 方法躏吊,所以說 join 方法的執(zhí)行是完全通過 wait 方法實現(xiàn)的  
    // 等待時間為 0 的時候氛改,就是無限等待,直到線程執(zhí)行完了
    if (millis == 0) {  
        // 如果當(dāng)前線程還存活的話比伏,就等待  
        while (isAlive()) {  
            // 調(diào)用該線程的 join 方法的線程拿到鎖之后進(jìn)行等待胜卤,直到線程執(zhí)行結(jié)束
            wait(0);  
        }  
    } else {  
        // 如果是等待的特定時間的話  
        while (isAlive()) {  
            long delay = millis - now;  
            if (delay <= 0) {  
                break;  
            }  
            wait(delay);  
            now = System.currentTimeMillis() - base;  
        }  
    }  
}  

從代碼上看,如果線程被生成了赁项,但還未被啟動葛躏,調(diào)用它的 join() 方法是沒有作用的澈段,將直接繼續(xù)向下執(zhí)行

join 方法實現(xiàn)是通過 wait(Object 提供的方法)。當(dāng) main 線程調(diào)用 t.join() 之前舰攒,main 線程必須擁有線程對象 t 的鎖败富,然后 main 線程調(diào)用 t.wait(time),直到等待時間 time 結(jié)束或者 t 線程執(zhí)行完畢即 t.isAlive() == false

Example 1

public class JoinTest implements Runnable {  
      
    public static int a = 0;  
  
    public void run() {  
        for (int k = 0; k < 5; k++) {  
            a = a + 1;  
        }  
    }  
  
    public static void main(String[] args) throws Exception {  
        Runnable r = new JoinTest();  
        Thread t = new Thread(r);  
        t.start();        
        System.out.println(a);  
    }         
}  

程序的輸出結(jié)果是 5 嗎摩窃?答案是:有可能兽叮。其實你很難遇到輸出 5 的時候,通常情況下都不是 5猾愿。為什么呢鹦聪?當(dāng)主線程 main 方法執(zhí)行 System.out.println(a) 這條語句時,線程還沒有真正開始運行蒂秘,或許正在為它分配資源準(zhǔn)備運行泽本。因為為線程分配資源需要時間,而 main 方法執(zhí)行完 t.start() 方法后繼續(xù)往下執(zhí)行 System.out.println(a)姻僧,這時得到的結(jié)果是 a 還沒有被改變的值 0 规丽。怎樣才能讓輸出結(jié)果為 5?其實很簡單撇贺,join() 方法提供了這種功能嘁捷。

public static void main(String[] args) throws Exception {  
    Runnable r = new JoinTest();  
    Thread t = new Thread(r);  
    t.start();        
    t.join();         // 加入 join()  
    System.out.println(a);  
}    

這個時候,程序輸入結(jié)果始終為 5显熏。

Example 2

class RunnableImpl implements Runnable {  
  
    public void run() {  
        try {  
            System.out.println("Begin sleep");  
            Thread.sleep(1000);  
            System.out.println("End sleep");  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
  
    }  
}  

public class JoinTest {  
      
    public static void main(String[] args) {  
        Thread t = new Thread(new RunnableImpl());  
        t.start();  
        try {  
            t.join(1000);  
            System.out.println("joinFinish");  
        } catch (InterruptedException e) {  
            e.printStackTrace();       
        }  
    }  
}  

結(jié)果是:

Begin sleep
End sleep
joinFinish

當(dāng) main 線程調(diào)用 t.join 時雄嚣,main 線程等待 t 線程,等待時間是 1000喘蟆,如果 t 線程 Sleep 2000 呢

class RunnableImpl implements Runnable {  
  
    public void run() {  
        try {  
            System.out.println("Begin sleep");  
            Thread.sleep(2000);     // 原來為 1000  
            System.out.println("End sleep");  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
  
    }  
}  

結(jié)果是:

Begin sleep
joinFinish
End sleep

也就是說 main 線程只等 1000 毫秒缓升,不管 t 什么時候結(jié)束

Example 3

class CustomThread1 extends 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");    
        }    
    }    
}    
  
class CustomThread extends Thread {    
    CustomThread1 t1;    
    public CustomThread(CustomThread1 t1) {            
        this.t1 = t1;    
    }    
    public void run() {    
        String threadName = Thread.currentThread().getName();    
        System.out.println(threadName + " start.");    
        try {    
            t1.join();    
            System.out.println(threadName + " end.");    
        } catch (Exception e) {    
            System.out.println("Exception from " + threadName + ".run");    
        }    
    }    
}    
  
public class JoinTestDemo {    
  
    public static void main(String[] args) {    
        String threadName = Thread.currentThread().getName();    
        System.out.println(threadName + " start.");    
        CustomThread1 t1 = new CustomThread1();    
        CustomThread t = new CustomThread(t1);    
        try {    
            t1.start();    
            Thread.sleep(2000);    
            t.start();    
            t.join();         // 下面會將此處注釋掉    
        } catch (Exception e) {    
            System.out.println("Exception from main");    
        }    
        System.out.println(threadName + " end!");    
    }    
}   

結(jié)果:

main start. // main 方法所在的線程起動,但沒有馬上結(jié)束蕴轨,因為調(diào)用 t.join()港谊,所以要等到 t 結(jié)束了,此線程才能向下執(zhí)行
[CustomThread1] Thread start. // 線程 CustomThread1 起動
[CustomThread1] Thread loop at 0 // 線程 CustomThread1 執(zhí)行
[CustomThread1] Thread loop at 1 // 線程 CustomThread1 執(zhí)行
[CustomThread] Thread start. // 線程 CustomThread 起動橙弱,但沒有馬上結(jié)束歧寺,因為調(diào)用 t1.join(),所以要等到 t1 結(jié)束了棘脐,此線程才能向下執(zhí)行
[CustomThread1] Thread loop at 2 // 線程 CustomThread1 繼續(xù)執(zhí)行
[CustomThread1] Thread loop at 3 // 線程 CustomThread1 繼續(xù)執(zhí)行
[CustomThread1] Thread loop at 4 // 線程 CustomThread1 繼續(xù)執(zhí)行
[CustomThread1] Thread end. // 線程 CustomThread1 結(jié)束了
[CustomThread] Thread end. // 線程 CustomThread 在 t1.join() 阻塞處起動斜筐,向下繼續(xù)執(zhí)行的結(jié)果
main end! // 線程 CustomThread 結(jié)束,此線程在 t.join() 阻塞處起動蛀缝,向下繼續(xù)執(zhí)行的結(jié)果

將上例中的 join 注釋掉后結(jié)果為:

main start. // main 方法所在的線程起動顷链,但沒有馬上結(jié)束,這里并不是因為 join 方法屈梁,而是因為 Thread.sleep(2000)
[CustomThread1] Thread start. // 線程 CustomThread1 起動
[CustomThread1] Thread loop at 0 // 線程 CustomThread1 執(zhí)行
[CustomThread1] Thread loop at 1 // 線程 CustomThread1 執(zhí)行
main end! // Thread.sleep(2000) 結(jié)束嗤练,雖然在線程 CustomThread 執(zhí)行了 t1.join()榛了,但這并不會影響到其他線程(這里 main 方法所在的線程)
[CustomThread] Thread start. // 線程 CustomThread 起動,但沒有馬上結(jié)束煞抬,因為調(diào)用 t1.join()霜大,所以要等到 t1 結(jié)束了,此線程才能向下執(zhí)行革答。
[CustomThread1] Thread loop at 2 // 線程 CustomThread1 繼續(xù)執(zhí)行
[CustomThread1] Thread loop at 3 // 線程 CustomThread1 繼續(xù)執(zhí)行
[CustomThread1] Thread loop at 4 // 線程 CustomThread1 繼續(xù)執(zhí)行
[CustomThread1] Thread end. // 線程 CustomThread1 結(jié)束了
[CustomThread] Thread end. // 線程 CustomThread 在 t1.join() 阻塞處起動战坤,向下繼續(xù)執(zhí)行的結(jié)果

Example 4

main 線程調(diào)用 t.join 時,必須能夠拿到線程 t 對象的鎖蝗碎,如果拿不到它是無法 wait 的,Example 2 中的
t.join(1000) 說明了 main 線程只等待 1 秒旗扑,但如果在它等待之前蹦骑,其他線程獲取了 t 對象的鎖,它等待時間可不止 1 秒了

public class ThreadJoinTest {

    public static void main(String[] args) {
        Thread t = new Thread(new RunnableImpl());
        new ThreadTest(t).start();
        t.start();
        try {
            t.join(1000);           // main 線程等 1s
            System.out.println("join Finish");
        } catch (InterruptedException e) {
            e.printStackTrace();

        }
//        System.out.println("join Finish");
    }
}

class RunnableImpl implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("Thread Begin sleep " + System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println("Thread End sleep " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadTest extends Thread {
    Thread thread;
    public ThreadTest(Thread thread) {
        this.thread = thread;
    }

    @Override
    public void run() {
        holdThreadLock();
    }

    public void holdThreadLock() {
        //用當(dāng)前的線程當(dāng)做lock
        synchronized (thread) {
            System.out.println("ThreadTest getObjectLock " + System.currentTimeMillis());
            try {
                Thread.sleep(9 * 1000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            System.out.println("ThreadTest ReleaseObjectLock " + System.currentTimeMillis());
        }

    }
}

注意:Thread.sleep() 會使當(dāng)前運行的線程交出執(zhí)行權(quán)臀防。Thread.sleep(0) 的作用眠菇,就是 “觸發(fā)操作系統(tǒng)立刻重新進(jìn)行一次 CPU 競爭”。

結(jié)果是:

ThreadTest getObjectLock 1519645264719
Thread Begin sleep 1519645264719
Thread End sleep 1519645266719
ThreadTest ReleaseObjectLock 1519645273720
join Finish

Thread Begin sleep 1519644950436
ThreadTest getObjectLock 1519644950436
Thread End sleep 1519644952437
ThreadTest ReleaseObjectLock 1519644959438
join Finish

在 main 方法中袱衷,通過 new ThreadTest(t).start() 實例化 ThreadTest 線程對象捎废,它通過 synchronized (thread) ,獲取線程對象 t 的鎖致燥,并 sleep(9000) 后釋放登疗,這就意味著,即使 main 方法 t.join(1000) 等待一秒鐘嫌蚤,它也必須等待 ThreadTest 線程釋放 t 鎖后才能進(jìn)入 wait 方法中辐益。

注意:t.join(1000) 是讓 main 線程等待 1000ms 或 t 死掉后執(zhí)行,所以 t.join(1000) 和 sleep(9000) 是同時的脱吱,ThreadTest 的實際等待時間還是 9s

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末智政,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子箱蝠,更是在濱河造成了極大的恐慌续捂,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宦搬,死亡現(xiàn)場離奇詭異牙瓢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)间校,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門一罩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撇簿,你說我怎么就攤上這事聂渊〔罟海” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵汉嗽,是天一觀的道長欲逃。 經(jīng)常有香客問我,道長饼暑,這世上最難降的妖魔是什么稳析? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮弓叛,結(jié)果婚禮上彰居,老公的妹妹穿的比我還像新娘。我一直安慰自己撰筷,他們只是感情好陈惰,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著毕籽,像睡著了一般抬闯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上关筒,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天溶握,我揣著相機(jī)與錄音,去河邊找鬼蒸播。 笑死睡榆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袍榆。 我是一名探鬼主播肉微,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜡塌!你這毒婦竟也來了碉纳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤馏艾,失蹤者是張志新(化名)和其女友劉穎劳曹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琅摩,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡铁孵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了房资。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜕劝。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出岖沛,到底是詐尸還是另有隱情暑始,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布婴削,位于F島的核電站廊镜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏唉俗。R本人自食惡果不足惜嗤朴,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望虫溜。 院中可真熱鬧雹姊,春花似錦、人聲如沸衡楞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寺酪。三九已至坎背,卻和暖如春替劈,著一層夾襖步出監(jiān)牢的瞬間寄雀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工陨献, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留盒犹,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓眨业,卻偏偏與公主長得像急膀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子龄捡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容