工做三年,小胖連 Thread 源碼都沒看過?真的菜!

金三銀四,不少小夥伴都打算跳槽。而多線程是面試必問的,給你們分享下 Thread 源碼解析,也算是我本身的筆記整理、思惟覆盤。學習的同時,順便留下點什麼~前端

一、設置線程名

在使用多線程的時候,想要查看線程名是很簡單的,調用 Thread.currentThread().getName() 便可。默認狀況下,主線程名是 main,其餘線程名是 Thread-x,x 表明第幾個線程java

咱們點進去構造方法,看看它是怎麼命名的:調用了 init 方法,init 方法內部調用了 nextThreadNum 方法。面試

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

nextThreadNum 是一個線程安全的方法,同一時間只可能有一個線程修改,而 threadInitNumber 是一個靜態變量,它能夠被類的全部對象訪問。因此,每一個線程在建立時直接 +1 做爲子線程後綴。算法

/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

再看 init 方法,注意到最後有 this.name = name 賦值給 volatile 變量的 name,默認就是用 Thread-x 做爲子線程名。數據庫

private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
    init(g, target, name, stackSize, null, true);
}


private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    // 名稱賦值
    this.name = name;
    // 省略代碼
}

最終 getName 方法拿到的就是這個 volatile 變量 name 的值。編程

private volatile String name;

public final String getName() {
    return name;
}

注意到源碼中,有帶 name 參數的構造方法:設計模式

public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

因此,咱們能夠初始化時就指定線程名安全

public class MyThread implements Runnable {

    @Override
    public void run() {
        // 打印當前線程的名字
        System.out.println(Thread.currentThread().getName());
    }
}
public class TestMain {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();

        //帶參構造方法給線程起名字
        Thread thread1 = new Thread(myThread, "一個優秀的廢人");
        Thread thread2 = new Thread(myThread, "在複習多線程");

        // 啓動線程
        thread1.start();
        thread2.start();

        // 打印當前線程的名字
        System.out.println(Thread.currentThread().getName());
    }
}

二、線程優先級

在 Thread 源碼中和線程優先級相關的屬性有如下 3 個:微信

// 線程能夠擁有的最小優先級
public final static int MIN_PRIORITY = 1;

// 線程默認優先級
public final static int NORM_PRIORITY = 5;

// 線程能夠擁有的最大優先級
public final static int MAX_PRIORITY = 10

線程的優先級能夠理解爲線程搶佔 CPU 時間片(也就是執行權)的機率,優先級越高概率越大,但並不意味着優先級高的線程就必定先執行。數據結構

Thread 類中,設置優先級的源碼以下:

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    // 先驗證優先級的合理性,不能大於 10,也不能小於 1
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        // 優先級若是超過線程組的最高優先級,則把優先級設置爲線程組的最高優先級(有種一人得道雞犬升天的感受~)
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        // native 方法
        setPriority0(priority = newPriority);
    }
}

在 java 中,咱們通常這樣設置線程的優先級:

public class TestMain {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();

        //帶參構造方法給線程起名字
        Thread thread1 = new Thread(myThread, "一個優秀的廢人");
        Thread thread2 = new Thread(myThread, "在複習多線程");

        // 設置優先級
        thread1.setPriority(1);
        thread2.setPriority(10);

        // 啓動線程
        thread1.start();
        thread2.start();

        // 打印當前線程的名字
        System.out.println(Thread.currentThread().getName());
    }
}

三、守護線程

守護線程是低優先級的線程,專門爲其餘線程服務的,其餘線程執行完了,它也就掛了。在 java 中,咱們的垃圾回收線程就是典型的守護線程

它有兩個特色:

  • 當別的非守護線程執行完了,虛擬機就會退出,守護線程也就會被中止掉。
  • 守護線程做爲一個服務線程,沒有服務對象就沒有必要繼續運行了

舉個栗子:你能夠把守護線程理解爲公司食堂裏面的員工,專門爲辦公室員工提供飲食服務,辦公室員工下班回家了,它們也就都回家了。因此,不能使用守護線程訪問資源(好比修改數據、進行I/O 操做等等),由於這貨隨時掛掉。反之,守護線程常常被用來執行一些後臺任務,可是呢,你又但願在程序退出時,或者說 JVM 退出時,線程可以自動關閉,此時,守護線程是你的首選

在 java 中,能夠經過 setDaemon 能夠設置守護線程,源碼以下:

public final void setDaemon(boolean on) {
    // 判斷是否有權限
    checkAccess();
    // 判斷是否活躍
    if (isAlive()) {
        throw new IllegalThreadStateException();
    }
    daemon = on;
}

從以上源碼,能夠知道必須在線程啓動以前就把目標線程設置爲守護線程,不然報錯

例子:新增一個 DaemonThread,裏面執行的任務是死循環不斷打印本身的線程名字。

public class DaemonThread implements Runnable {

    @Override
    public void run() {
        // 死循環
        while(true) {
          // 打印當前線程的名字
          System.out.println(Thread.currentThread().getName());
        }
    }

}

測試:在啓動以前先把 thread2 設置爲守護線程,thread1 啓動,再啓動 thread2 。

public class TestMain {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        DaemonThread  daemonThread = new DaemonThread();

        //帶參構造方法給線程起名字
        Thread thread1 = new Thread(myThread, "一個優秀的廢人");
        Thread thread2 = new Thread(daemonThread, "在複習多線程");

        // 設置 thread2 爲守護線程
        thread2.setDaemon(true);

        // 啓動線程
        thread1.start();
        thread2.start();

        // 打印當前線程的名字
        System.out.println(Thread.currentThread().getName());
    }
}

正常來講,若是 thread2 不是守護線程,JVM 不會退出,除非發生嚴重的異常,thread2 會一直死循環在控制檯打印本身的名字。然而,設置爲守護線程以後,JVM 退出,thread2 也再也不執行

守護線程.png

四、start() 和 run() 有啥區別?

首先從 Thread 源碼來看,start () 方法屬於 Thread 自身的方法,而且使用了 synchronized 來保證線程安全,源碼以下:

public synchronized void start() {

        // 一、狀態驗證,不等於 NEW 的狀態會拋出異常
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        // 二、通知線程組,此線程即將啓動
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                // 三、不處理任何異常,若是 start0 拋出異常,則它將被傳遞到調用堆棧上
            }
        }
}

而 run () 方法爲 Runnable 的抽象方法,必須由調用類重寫此方法,重寫的 run () 方法其實就是此線程要執行的業務方法,源碼以下:

public class Thread implements Runnable {
    // 忽略其餘方法......
    private Runnable target;
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

關於二者區別這個問題,其實上次寫多線程的開篇,已經說過了,有興趣的戳:
這裏長話短說,它的區別是:

  • run 方法裏面定義的是線程執行的任務邏輯,直接調用跟普通方法沒啥區別
  • start 方法啓動線程,使線程由 NEW 狀態轉爲 RUNNABLE,而後再由 jvm 去調用該線程的 run () 方法去執行任務
  • start 方法不能被屢次調用,不然會拋出 java.lang.IllegalStateException;而 run () 方法能夠進行屢次調用,由於它是個普通方法

五、sleep 方法

sleep 方法的源碼入下,它是個 native 方法。咱們無法看源碼,只能經過註釋來理解它的含義,我配上了簡短的中文翻譯,總結下來有三點注意:

  • 睡眠指定的毫秒數,且在這過程當中不釋放鎖
  • 若是參數非法,報 IllegalArgumentException
  • 睡眠狀態下能夠響應中斷信號,並拋出 InterruptedException(後面會說)
  • 調用 sleep 方法,即會從 RUNNABLE 狀態進入 Timed Waiting(計時等待)狀態
/**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.

// 一、睡眠指定的毫秒數,且在這過程當中不釋放鎖

     * @param  millis
     *         the length of time to sleep in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative

// 二、若是參數非法,報 IllegalArgumentException
     
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.

// 三、睡眠狀態下能夠響應中斷信號,並拋出 InterruptedException

*/
public static native void sleep(long millis) throws InterruptedException;

六、如何正確中止線程?

線程在不一樣的狀態下遇到中斷會產生不一樣的響應,有點會拋出異常,有的則沒有變化,有的則會結束線程。

如何正確中止線程?有人說這不簡單嘛。直接 stop 方法,stop 方法強制終止線程,因此它是不行的。它已經被 Java 設置爲 @Deprecated 過期方法了。

主要緣由是stop 太暴力了,沒有給線程足夠的時間來處理在線程中止前保存數據的邏輯,任務就中止了,會致使數據完整性的問題

舉個栗子:線程正在寫入一個文件,這時收到終止信號,它就須要根據自身業務判斷,是選擇當即中止,仍是將整個文件寫入成功後中止,而若是選擇當即中止就可能形成數據不完整,無論是中斷命令發起者,仍是接收者都不但願數據出現問題。

通常狀況下,使用 interrupt 方法來請求中止線程,它並非直接中止。它僅僅是給這個線程發了一個信號告訴它,它應該要結束了 (明白這一點很是重要!),而要不要立刻中止,或者過一段時間後中止,甚至壓根不中止都是由被中止的線程根據本身的業務邏輯來決定的

要了解 interrupt 怎麼使用,先來看看源碼(已經給了清晰的註釋):

/**
     * Interrupts this thread.

一、只能本身中斷本身,否則會拋出 SecurityException 

     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.

二、若是線程調用 wait、sleep、join 等方法,進入了阻塞,
會形成調用中斷無效,拋 InterruptedException 異常。

     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.

三、以上三種狀況都不會發生時,纔會把線程的中斷狀態改變

     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>

四、中斷已經掛了的線程是無效的

     * <p> Interrupting a thread that is not alive need not have any effect.
     *
     * @throws  SecurityException
     *          if the current thread cannot modify this thread
     *
     * @revised 6.0
     * @spec JSR-51
     */
    public void interrupt() {
        // 檢查是否有權限
        if (this != Thread.currentThread())
            checkAccess();
        
        synchronized (blockerLock) {
             // 判斷是否是阻塞狀態的線程調用,好比剛調用 sleep()
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                // 若是是,拋異常同時推出阻塞。將中斷標誌位改成 false
                b.interrupt(this);
                return;
            }
        }
        // 不然,順利改變標誌位
        interrupt0();
    }

interrupt 方法提到了四個點:

  • 只能本身中斷本身,否則會拋出 SecurityException
  • 若是線程調用 wait、sleep、join 等方法進入了阻塞,會形成調用中斷無效,拋 InterruptedException 異常。
  • 以上狀況不會發生時,纔會把線程的中斷狀態改變
  • 中斷已經掛了的線程是無效的

除此之外,java 中跟中斷有關的方法還有 interrupted()isInterrupted(),看看源碼:

/**
 * Tests whether the current thread has been interrupted.  The
 * <i>interrupted status</i> of the thread is cleared by this method.  In
 * other words, if this method were to be called twice in succession, the
 * second call would return false (unless the current thread were
 * interrupted again, after the first call had cleared its interrupted
 * status and before the second call had examined it).
 *
 * <p>A thread interruption ignored because a thread was not alive
 * at the time of the interrupt will be reflected by this method
 * returning false.
 *
 * @return  <code>true</code> if the current thread has been interrupted;
 *          <code>false</code> otherwise.
 * @see #isInterrupted()
 * @revised 6.0
 */
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

/**
 * Tests whether this thread has been interrupted.  The <i>interrupted
 * status</i> of the thread is unaffected by this method.
 *
 * <p>A thread interruption ignored because a thread was not alive
 * at the time of the interrupt will be reflected by this method
 * returning false.
 *
 * @return  <code>true</code> if this thread has been interrupted;
 *          <code>false</code> otherwise.
 * @see     #interrupted()
 * @revised 6.0
 */
public boolean isInterrupted() {
    return isInterrupted(false);
}

/**
 * Tests if some Thread has been interrupted.  The interrupted state
 * is reset or not based on the value of ClearInterrupted that is
 * passed.
 */
private native boolean isInterrupted(boolean ClearInterrupted);

兩個點:

  • isInterrupted() 用於判斷中斷標誌位,調用不會影響當前標誌位
  • interrupted() 用於清除中斷標誌位,調用會清除標誌位

前面說了,interrupt 只是發個信號給線程,視線程狀態把它的中斷標誌位設爲 true 或者清除(設置爲 false),那它會改變線程狀態嗎?前文《線程的狀態》說過線程有 6 種狀態,咱們來驗證每種狀態的中斷響應以及狀態變動狀況:

NEW & TERMINATED

public class StopThread implements Runnable {

    @Override
    public void run() {
        // do something
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        System.out.println(thread.isInterrupted());
    }
}

運行結果:線程並沒啓動,標誌不生效

結果

public class StopThread implements Runnable {

    @Override
    public void run() {
        // do something
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();

        thread.join();
        System.out.println(thread.getState());

        thread.interrupt();

        System.out.println(thread.isInterrupted());
    }
}

運行結果:線程已掛,標誌並不生效

結果

RUNNABLE

public class StopThread implements Runnable {

    @Override
    public void run() {
        int count = 0;
        while (true) {
            if (count < 10) {
                System.out.println(count++);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        // 查看狀態
        System.out.println(thread.getState());
        thread.interrupt();
        // 等待 thread 中斷
        Thread.sleep(500);
        // 查看標誌位
        System.out.println(thread.isInterrupted());
        // 查看狀態
        System.out.println(thread.getState());
    }
}

運行結果:僅僅設置中斷標誌位,JVM 並無退出,線程仍是處於 RUNNABLE 狀態。

結果

看到這裏,有人可能說老子中斷了個寂寞???正確的中斷寫法應該是這樣的:咱們經過 Thread.currentThread ().isInterrupt () 判斷線程是否被中斷,隨後檢查是否還有工做要作。正確的中止線程寫法應該是這樣的:

while (!Thread.currentThread().islnterrupted() && more work to do) {
    do more work
}

在 while 中,經過 Thread.currentThread ().isInterrupt () 判斷線程是否被中斷,隨後檢查是否還有工做要作。&& 表示只有當兩個判斷條件同時知足的狀況下,纔會去執行線程的任務。實際例子:

public class StopThread implements Runnable {

    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count = " + count++);
        }
        System.out.println("響應中斷退出線程");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        // 查看狀態
        System.out.println(thread.getState());
        // 中斷
        thread.interrupt();
        // 查看標誌位
        System.out.println(thread.isInterrupted());
        // 等待 thread 中斷
        Thread.sleep(500);
        // 查看標誌位
        System.out.println(thread.isInterrupted());
        // 查看狀態
        System.out.println(thread.getState());
    }
}

結果

個人業務是從 0 開始計數,大於 1000 或者線程接收到中斷信號就中止計數調用 interrupt ,該線程檢測到中斷信號,中斷標記位就會被設置成 true,因而在還沒打印完 1000 個數的時候就會停下來。這樣就不會有安全問題。這種就屬於經過 interrupt 正確中止線程的狀況

BLOCKED

首先,啓動線程一、2,調用 synchronized 修飾的方法,thread1 先啓動佔用鎖,thread2 將進入 BLOCKED 狀態。

public class StopDuringSleep implements Runnable {

    public synchronized static void doSomething(){
        while(true){
            //do something
        }
    }

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

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new StopDuringSleep());
        thread1.start();

        Thread thread2 = new Thread(new StopDuringSleep());
        thread2.start();

        Thread.sleep(1000);
        System.out.println(thread1.getState());
        System.out.println(thread2.getState());

        thread2.interrupt();
        System.out.println(thread2.isInterrupted());
        System.out.println(thread2.getState());
    }
}

運行結果:跟 RUNNABLE 同樣,能響應中斷。

結果

sleep 期間(WAITING 狀態)可否感覺到中斷?

上面講 sleep 方法時說過, sleep 是能夠響應立刻中斷信號,並清除中斷標誌位(設置爲 false),同時拋出 InterruptedException 異常,退出計時等待狀態。看看例子:主線程休眠 5 毫秒後,通知子線程中斷,此時子線程仍在執行 sleep 語句,處於休眠中。

public class StopDuringSleep implements Runnable {

    @Override
    public void run() {
        int count = 0;
        try {
            while (!Thread.currentThread().isInterrupted() && count < 1000) {
                System.out.println("count = " + count++);
                // 子線程 sleep
                Thread.sleep(1000000);
            }
        } catch (InterruptedException e) {
            // 判斷該線程的中斷標誌位狀態
            System.out.println(Thread.currentThread().isInterrupted());
            // 打印線程狀態
            System.out.println(Thread.currentThread().getState());
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopDuringSleep());
        thread.start();
        // 主線程 sleep
        Thread.sleep(5);
        thread.interrupt();
    }

}

運行結果:interrupt 會把處於 WAITING 狀態線程改成 RUNNABLE 狀態

運行結果

僅僅 catch 異常就夠了嗎?

實際開發中每每是團隊協做,互相調用。咱們的方法中調用了 sleep 或者 wait 等能響應中斷的方法時,僅僅 catch 住異常而不處理是很是不友好的。這種行爲叫屏蔽了中斷請求

那怎麼作才能避免這種狀況呢?首先能夠在方法簽名中拋出異常,好比:

void subTask2() throws InterruptedException {
    Thread.sleep(1000);
}

Java中,異常確定是有調用方處理的。調用方要麼本身拋到上層,要麼 try catch 處理。若是每層邏輯都遵照規範,將中斷信號傳遞到頂層,最終讓 run () 方法能夠捕獲到異常。雖然 run 方法自己沒有拋出 checkedException 的能力,但它能夠經過 try/catch 根據業務邏輯來處理異常

除此之外,還能夠在 catch 語句中再次中斷線程。好比上述例子中,咱們能夠在 catch 中這樣寫:

try {
    // 省略代碼
} catch (InterruptedException e) {
    // 判斷該線程的中斷標誌位狀態
    System.out.println(Thread.currentThread().isInterrupted());
    // 打印線程狀態
    System.out.println(Thread.currentThread().getState());
    // 再次中斷
    Thread.currentThread().interrupt();
    // 判斷該線程的中斷標誌位狀態
    System.out.println(Thread.currentThread().isInterrupted());
    e.printStackTrace();
}

運行結果:

運行結果

sleep 期間被中斷,會清除中斷信號將其置爲 false。這時就須要手動在 catch 中再次設置中斷信號。如此,中斷信號依然能夠被檢測,後續方法仍可知道這裏發生過中斷,並作出相應邏輯處理

結論:NEW 和 TERMINATED 狀態的線程不響應中斷,其餘狀態可響應;同時 interrupt 會把 WAITING & TimeWAITING 狀態的線程改成 RUNNABLE

七、yield 方法

看 Thread 的源碼能夠知道 yield () 爲本地方法,也就是說 yield () 是由 C 或 C++ 實現的,源碼以下:

/**
 * A hint to the scheduler that the current thread is willing to yield
 * its current use of a processor. The scheduler is free to ignore this
 * hint.
 *
 * <p> Yield is a heuristic attempt to improve relative progression
 * between threads that would otherwise over-utilise a CPU. Its use
 * should be combined with detailed profiling and benchmarking to
 * ensure that it actually has the desired effect.
 *
 * <p> It is rarely appropriate to use this method. It may be useful
 * for debugging or testing purposes, where it may help to reproduce
 * bugs due to race conditions. It may also be useful when designing
 * concurrency control constructs such as the ones in the
 * {@link java.util.concurrent.locks} package.
 */
public static native void yield();

看代碼註釋知道:

  • 當前線程調用 yield() 會讓出 CPU 使用權,給別的線程執行,可是不確保真正讓出。誰先搶到 CPU 誰執行。
  • 當前線程調用 yield() 方法,會將狀態從 RUNNABLE 轉換爲 WAITING。

好比:

public static void main(String[] args) throws InterruptedException {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("線程:" +
                    Thread.currentThread().getName() + " I:" + i);
                if (i == 5) {
                    Thread.yield();
                }
            }
        }
    };
    Thread t1 = new Thread(runnable, "T1");
    Thread t2 = new Thread(runnable, "T2");
    t1.start();
    t2.start();
}

執行這段代碼會發現,每次的執行結果都不同。那是由於 yield 方法很是不穩定。

八、join 方法

調用 join 方法,會等待該線程執行完畢後才執行別的線程。按照慣例,先來看看源碼:

/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. As a thread terminates the
 * {@code this.notifyAll} method is invoked. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.
 *
 * @param  millis
 *         the time to wait in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    // 超時時間不能小於 0
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    // 等於 0 表示無限等待,直到線程執行完爲之
    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 () 方法來實現的。
  • 當前線程終止,會調用當前實例的 notifyAll 方法喚醒其餘線程。
  • 調用 join 方法,會使當前線程從 RUNNABLE 狀態轉至 WAITING 狀態。

總結

Thread 類中主要有 start、run、sleep、yield、join、interrupt 等方法,其中start、sleep、yield、join、interrupt(改變 sleep 狀態)是會改變線程狀態的。最後,上一張完成版的線程狀態切換圖

線程的 6 種狀態

福利

若是看到這裏,喜歡這篇文章的話,請幫點個好看。微信搜索一個優秀的廢人,關注後回覆電子書送你 100+ 本編程電子書 ,不僅 Java 哦,詳情看下圖。回覆 1024送你一套完整的 java 視頻教程。

資源

C語言

C++

Java

Git

Python

GO

Linux

經典必讀

面試相關

前端

人工智能

設計模式

數據庫

數據結構與算法

計算機基礎

一個優秀的廢人

相關文章
相關標籤/搜索