Java Thread的join() 之刨根問底

0.Join()

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

join有3個重載的方法:併發

void join():當前線程等該加入該線程後面,等待該線程終止。
void join(long millis):當前線程等待該線程終止的時間最長爲 millis 毫秒。 若是在millis時間內,該線程沒有執行完,那麼當前線程進入就緒狀態,從新等待cpu調度。
void join(long millis,int nanos):等待該線程終止的時間最長爲 millis 毫秒 + nanos納秒。若是在millis時間內,該線程沒有執行完,那麼當前線程進入就緒狀態,從新等待cpu調度。ide

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

1.使用方法

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

public class MyThread extends Thread {

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

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

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("~~~~~~~~~~~~~~~");

        }
    }
}
複製代碼

輸出結果以下:this

子線程執行完畢
主線程執行完畢
~~~~~~~~~~~~~~~
子線程執行完畢
主線程執行完畢
~~~~~~~~~~~~~~~
子線程執行完畢
主線程執行完畢
~~~~~~~~~~~~~~~
子線程執行完畢
主線程執行完畢
~~~~~~~~~~~~~~~
子線程執行完畢
主線程執行完畢
~~~~~~~~~~~~~~~
複製代碼

結果分析: 子線程每次都在主線程以前執行完畢,即子線程會在主線程以前執行。spa

代碼中循環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++編寫的本地方法。
複製代碼
相關文章
相關標籤/搜索