Thread之三:Thread Join()的用法


join()的使用場景

在很多情況下,主線程創建並啟動子線程,如果子線程中要進行大量的耗時運算,主線程將可能早於子線程結束。如果主線程需要知道子線程的執行結果時,就需要等待子線程執行結束了。主線程可以sleep(xx),但這樣的xx時間不好確定,因為子線程的執行時間不確定,join()方法比較合適這個場景。

 

join()方法:

join()是Thread類的一個方法。根據jdk文檔的定義:

public final void join()throws InterruptedException: Waits for this thread to die.

join()方法的作用,是等待這個線程結束;但顯然,這樣的定義並不清晰。個人認為"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()方法,此時主線程需要等待(阻塞),等待子線程結束了(Waits for this thread to die.),才能繼續執行t.join()之后的代碼塊。

 

 join() 源碼分析

    /**Waits for this thread to die.*/
    public final void join() throws InterruptedException {
        join(0);
    }

再看join(long millis)方法:

/**
 * Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever. 
 * This implementation uses a loop of this.wait calls conditioned on this.isAlive. As a thread terminates the this.notifyAll method is invoked. It is recommended that applications not use wait, notify, or notifyAll on Thread instances.
 */
    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方法實現是通過wait(小提示:Object 提供的方法)。 當main線程調用t.join時候,main線程會獲得線程對象t的鎖(wait 意味着拿到該對象的鎖),調用該對象的wait(等待時間),直到該對象喚醒main線程 ,比如退出后。這就意味着main 線程調用t.join時,必須能夠拿到線程t對象的鎖。

join() 一共有三個重載版本,分別是無參、一個參數、兩個參數:

 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,同步的對象是當前線程實例。

(2) 無參版本和兩個參數版本最終都調用了一個參數的版本。

(3) join() 和 join(0) 是等價的,表示一直等下去;join(非0)表示等待一段時間。

從源碼可以看到 join(0) 調用了Object.wait(0),其中Object.wait(0) 會一直等待,直到被notify/中斷才返回。

while(isAlive())是為了防止子線程偽喚醒(spurious wakeup),只要子線程沒有TERMINATED的,父線程就需要繼續等下去。

(4) join() 和 sleep() 一樣,可以被中斷(被中斷時,會拋出 InterrupptedException 異常);不同的是,join() 內部調用了 wait(),會出讓鎖,而 sleep() 會一直保持鎖

 

join使用時注意幾點:

1、join與start調用順序問題

  上面的討論大概知道了join的作用了,那么,入股 join在start前調用,會出現什么后果呢?先看下面的測試結果

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!");
    }
}

執行結果:

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]執行完再執行。join方法必須在線程start方法調用之后調用才有意義。這個也很容易理解:如果一個線程都沒有start,那它也就無法同步了。

2、join()與異常

在join()過程中,如果當前線程被中斷,則當前線程出現異常。(注意是調用thread.join()的線程被中斷才會進入異常,比如a線程調用b.join(),a中斷會報異常而b中斷不會異常)

如下:threadB中啟動threadA,並且調用其方法等待threadA完成,此時向threadB發出中斷信號,會進入中斷異常代碼。

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        BThread bt = new BThread();
//        try {
            Thread.sleep(2000);
            Thread.currentThread().interrupt();
            bt.start();
            bt.join(); //        } catch (Exception e) {
//            System.out.println("Exception from main");
//        }
        System.out.println(threadName + " end!");
    }
}

 

執行結果:

main start.
[BThread] Thread start.
[BThread] Thread loop at 0
Exception in thread "main" java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at java.lang.Thread.join(Thread.java:1249)
    at java.lang.Thread.join(Thread.java:1323)
    at com.dxz.join.TestDemo.main(TestDemo.java:46)
[BThread] Thread loop at 1
[BThread] Thread loop at 2
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end.

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM