【Java】Thread類中的join()方法原理

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

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

join()方法的做用,是等待這個線程結束;但顯然,這樣的定義並不清晰。我的認爲」Java 7 Concurrency Cookbook」的定義較爲清晰:ide

join() method suspends the execution of the calling thread until the object called finishes its execution.this

也就是說,t.join()方法阻塞調用此方法的線程(calling thread),直到線程t完成,此線程再繼續;一般用於在main()主線程內,等待其它線程完成再結束main()主線程。咱們來看看下面的例子。.net

例子
咱們對比一下下面這兩個例子,看看使用join()方法的做用是什麼?線程

不使用join()方法的狀況:
public static void main(String[] args){
    System.out.println("MainThread run start.");對象

    //啓動一個子線程
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("threadA run start.");
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("threadA run finished.");
        }
    });
    threadA.start();blog

    System.out.println("MainThread join before");
    System.out.println("MainThread run finished.");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
運行結果以下:文檔

MainThread run start. 
threadA run start. 
MainThread join before 
MainThread run finished. 
threadA run finished.get

由於上述子線程執行時間相對較長,因此是在主線程執行完畢以後才結束。

使用了join()方法的狀況:
public static void main(String[] args){
    System.out.println("MainThread run start.");

    //啓動一個子線程
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("threadA run start.");
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("threadA run finished.");
        }
    });
    threadA.start();

    System.out.println("MainThread join before");
    try {
        threadA.join();    //調用join()
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("MainThread run finished.");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
運行結果以下:

MainThread run start. 
threadA run start. 
MainThread join before 
threadA run finished. 
MainThread run finished.

對子線程threadA使用了join()方法以後,咱們發現主線程會等待子線程執行完成以後才日後執行。

join()的原理和做用
java層次的狀態轉換圖

咱們來深刻源碼瞭解一下join():

//Thread類中
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) {    //這個分支是無限期等待直到b線程結束
        while (isAlive()) {
            wait(0);
        }
    } else {    //這個分支是等待固定時間,若是b沒結束,那麼就不等待了。
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
咱們重點關注一下這兩句,無限期等待的狀況::

while (isAlive()) {
    wait(0);    //wait操做,那必然有synchronized與之對應
}
1
2
3
注意這個wait()方法是Object類中的方法,再來看sychronized的是誰:

public final synchronized void join(long millis) throws InterruptedException { ... }
1
成員方法加了synchronized說明是synchronized(this),this是誰啊?this就是threadA子線程對象自己。也就是說,主線程持有了threadA這個對象的鎖。

你們都知道,有了wait(),必然有notify(),何時纔會notify呢?在jvm源碼裏:

// 位於/hotspot/src/share/vm/runtime/thread.cpp中
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
    // ...

    // Notify waiters on thread object. This has to be done after exit() is called
    // on the thread (if the thread is the last thread in a daemon ThreadGroup the
    // group should have the destroyed bit set before waiters are notified).
    // 有一個賊不起眼的一行代碼,就是這行
    ensure_join(this);

    // ...
}


static void ensure_join(JavaThread* thread) {
    // We do not need to grap the Threads_lock, since we are operating on ourself.
    Handle threadObj(thread, thread->threadObj());
    assert(threadObj.not_null(), "java thread object must exist");
    ObjectLocker lock(threadObj, thread);
    // Ignore pending exception (ThreadDeath), since we are exiting anyway
    thread->clear_pending_exception();
    // Thread is exiting. So set thread_status field in  java.lang.Thread class to TERMINATED.
    java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
    // Clear the native thread instance - this makes isAlive return false and allows the join()
    // to complete once we've done the notify_all below
    java_lang_Thread::set_thread(threadObj(), NULL);

    // 同志們看到了沒,別的不用看,就看這一句
    // thread就是當前線程,是啥?就是剛纔例子中說的threadA線程啊。
    lock.notify_all(thread);

    // Ignore pending exception (ThreadDeath), since we are exiting anyway
    thread->clear_pending_exception();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
當子線程threadA執行完畢的時候,jvm會自動喚醒阻塞在threadA對象上的線程,在咱們的例子中也就是主線程。至此,threadA線程對象被notifyall了,那麼主線程也就能繼續跑下去了。

能夠看出,join()方法實現是經過wait()(小提示:Object 提供的方法)。 當main線程調用threadA.join時候,main線程會得到線程對象threadA的鎖(wait 意味着拿到該對象的鎖),調用該對象的wait(等待時間),直到該對象喚醒main線程 (也就是子線程threadA執行完畢退出的時候)

總結 首先join() 是一個synchronized方法, 裏面調用了wait(),這個過程的目的是讓持有這個同步鎖的線程進入等待,那麼誰持有了這個同步鎖呢?答案是主線程,由於主線程調用了threadA.join()方法,至關於在threadA.join()代碼這塊寫了一個同步代碼塊,誰去執行了這段代碼呢,是主線程,因此主線程被wait()了。而後在子線程threadA執行完畢以後,JVM會調用lock.notify_all(thread);喚醒持有threadA這個對象鎖的線程,也就是主線程,會繼續執行。

相關文章
相關標籤/搜索