Java Thread的join() 之刨根問底

P城到底誰說的算發表於2018-06-25

0.Join()

執行緒的合併的含義就是 將幾個並行執行緒的執行緒合併為一個單執行緒執行,應用場景是 當一個執行緒必須等待另一個執行緒執行完畢才能執行時,Thread類提供了join方法來完成這個功能,注意,它不是靜態方法

join有3個過載的方法:

void join():當前執行緒等該加入該執行緒後面,等待該執行緒終止。
void join(long millis):當前執行緒等待該執行緒終止的時間最長為 millis 毫秒。 如果在millis時間內,該執行緒沒有執行完,那麼當前執行緒進入就緒狀態,重新等待cpu排程。
void join(long millis,int nanos):等待該執行緒終止的時間最長為 millis 毫秒 + nanos納秒。如果在millis時間內,該執行緒沒有執行完,那麼當前執行緒進入就緒狀態,重新等待cpu排程。

參考:猿碼道:啃碎併發(二)

1.使用方法

新建一個Thread類,重寫run()方法:

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("子執行緒執行完畢");
    }
}
複製程式碼

新建測試類,測試Join()方法:

public class TestThread {

    public static void main(String[] args) {
        //迴圈五次
        for (int i = 0; i < 5; i++) {

            MyThread thread = new MyThread();
            //啟動執行緒
            thread.start();
            try {
                //呼叫join()方法
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主執行緒執行完畢");
            System.out.println("~~~~~~~~~~~~~~~");

        }
    }
}
複製程式碼

輸出結果如下:

子執行緒執行完畢
主執行緒執行完畢
~~~~~~~~~~~~~~~
子執行緒執行完畢
主執行緒執行完畢
~~~~~~~~~~~~~~~
子執行緒執行完畢
主執行緒執行完畢
~~~~~~~~~~~~~~~
子執行緒執行完畢
主執行緒執行完畢
~~~~~~~~~~~~~~~
子執行緒執行完畢
主執行緒執行完畢
~~~~~~~~~~~~~~~
複製程式碼

結果分析: 子執行緒每次都在主執行緒之前執行完畢,即子執行緒會在主執行緒之前執行。

程式碼中迴圈5次是為了排除執行緒執行的隨機性,若不能說明問題,可以調大迴圈次數進行測試。

2.原理分析

檢視Thread類原始碼:

public final void join() throws InterruptedException {
    //當呼叫join()時,實際是呼叫join(long)方法
        join(0);
    }
複製程式碼

檢視Join(long)方法原始碼:

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) {  //由於上一步傳入引數為0,因此呼叫當前判斷
            while (isAlive()) { //判斷子執行緒是否存活
                wait(0); //呼叫wait(0)方法
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
複製程式碼

檢視isAlive()方法原始碼:

/**
     * Tests if this thread is alive. A thread is alive if it has
     * been started and has not yet died.
     * 測試執行緒是否還活著。如果執行緒存活的話它就是已經開始,還沒有死亡的狀態。
     * @return  <code>true</code> if this thread is alive;
     *          <code>false</code> otherwise.
     */****
    public final native boolean isAlive();
複製程式碼

說明: 該方法為本地方法,判斷執行緒物件是否存活,若執行緒物件呼叫了start()方法,在沒有死亡的情況下此判斷為true。在上述例子中,由於呼叫了子執行緒的start()方法,並且沒有結束操作,因此判斷true。

檢視wait()方法原始碼:

public final native void wait(long timeout) throws InterruptedException;
複製程式碼

說明: 該方法為本地方法,呼叫此方法的當前執行緒需要釋放鎖,並等待喚醒。在上述例子中,主執行緒呼叫子執行緒物件的join()方法,因此主執行緒在此位置需要釋放鎖,並進行等待。

wait()與wait(0)的區別:
檢視wait()方法原始碼,wait()方法只呼叫了wait(0),如下:

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

因此,wait()與wait(0)相同。
複製程式碼

我們來擼一擼上述步驟: 主執行緒wait()等待,子執行緒呼叫了run()執行,列印“子執行緒執行完畢”。此時,主執行緒還沒被喚醒,還沒有執行下面的操作。那麼,問題來了,誰?在什麼時候?喚醒了主執行緒呢?

檢視Thread類中存在exit()方法,原始碼如下:

 /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     * 這個方法由系統呼叫,當該執行緒完全退出前給它一個機會去釋放空間。
     */
private void exit() {
        if (group != null) {                //執行緒組在Thread初始化時建立,存有建立的子執行緒
            group.threadTerminated(this);   //呼叫threadTerminated()方法
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }
複製程式碼

通過debug,exit()線上程執行完run()方法之後會被呼叫,此時執行緒組中存在當前子執行緒,因此會呼叫執行緒組的threadTerminated()方法。 檢視ThreadGroup.threadTerminated()方法原始碼:

/** Notifies the group that the thread {@code t} has terminated.
  * 通知執行緒組,t執行緒已經終止。
  *
void threadTerminated(Thread t) {
        synchronized (this) {
            remove(t);                          //從執行緒組中刪除此執行緒

            if (nthreads == 0) {                //當執行緒組中執行緒數為0時
                notifyAll();                    //喚醒所有待定中的執行緒
            }
            if (daemon && (nthreads == 0) &&
                (nUnstartedThreads == 0) && (ngroups == 0))
            {
                destroy();
            }
        }
    }
複製程式碼

通過此方法,將子執行緒從執行緒組中刪除,並喚醒其他等待的執行緒。在上述例子中,此時子執行緒被銷燬,並釋放佔用的資源,並喚醒等待中的執行緒。而在join()方法中等待的主執行緒被喚醒,並獲得鎖,列印“主執行緒執行完畢”。

3.總結

Join()方法,使呼叫此方法的執行緒wait()(在例子中是main執行緒),直到呼叫此方法的執行緒物件(在例子中是MyThread物件)所在的執行緒(在例子中是子執行緒)執行完畢後被喚醒。

由於執行緒的啟動與銷燬其實是由作業系統進行操作,所以在描述的時候刻意略去,如果有疑惑的地方,可以檢視C++編寫的本地方法。
複製程式碼

相關文章