主頁 > 後端開發 > 作業三年,小胖連 Thread 原始碼都沒看過?真的菜!

作業三年,小胖連 Thread 原始碼都沒看過?真的菜!

2021-02-24 06:10:40 後端開發

金三銀四,很多小伙伴都打算跳槽,而多執行緒是面試必問的,給大家分享下 Thread 原始碼決議,也算是我自己的筆記整理、思維復盤,學習的同時,順便留下點什么~

1、設定執行緒名

在使用多執行緒的時候,想要查看執行緒名是很簡單的,呼叫 Thread.currentThread().getName() 即可,默認情況下,主執行緒名是 main,其他執行緒名是 Thread-x,x 代表第幾個執行緒

我們點進去構造方法,看看它是怎么命名的:呼叫了 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());
    }
}

2、執行緒優先級

在 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());
    }
}

3、守護執行緒

守護執行緒是低優先級的執行緒,專門為其他執行緒服務的,其他執行緒執行完了,它也就掛了,在 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

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

首先從 Thread 原始碼來看,start () 方法屬于 Thread 自身的方法,并且使用了 synchronized 來保證執行緒安全,原始碼如下:

public synchronized void start() {

        // 1、狀態驗證,不等于 NEW 的狀態會拋出例外
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        // 2、通知執行緒組,此執行緒即將啟動
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                // 3、不處理任何例外,如果 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 () 方法可以進行多次呼叫,因為它是個普通方法

5、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.

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

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

// 2、如果引數非法,報 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.

// 3、睡眠狀態下可以回應中斷信號,并拋出 InterruptedException

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

6、如何正確停止執行緒?

執行緒在不同的狀態下遇到中斷會產生不同的回應,有點會拋出例外,有的則沒有變化,有的則會結束執行緒,

如何正確停止執行緒?有人說這不簡單嘛,直接 stop 方法,stop 方法強制終止執行緒,所以它是不行的,它已經被 Java 設定為 @Deprecated 過時方法了,

主要原因是stop 太暴力了,沒有給執行緒足夠的時間來處理在執行緒停止前保存資料的邏輯,任務就停止了,會導致資料完整性的問題

舉個栗子:執行緒正在寫入一個檔案,這時收到終止信號,它就需要根據自身業務判斷,是選擇立即停止,還是將整個檔案寫入成功后停止,而如果選擇立即停止就可能造成資料不完整,不管是中斷命令發起者,還是接收者都不希望資料出現問題,

一般情況下,使用 interrupt 方法來請求停止執行緒,它并不是直接停止,它僅僅是給這個執行緒發了一個信號告訴它,它應該要結束了 (明白這一點非常重要!),而要不要馬上停止,或者過一段時間后停止,甚至壓根不停止都是由被停止的執行緒根據自己的業務邏輯來決定的

要了解 interrupt 怎么使用,先來看看原始碼(已經給了清晰的注釋):

 /**
     * Interrupts this thread.

1、只能自己中斷自己,不然會拋出 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.

2、如果執行緒呼叫 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.

3、以上三種情況都不會發生時,才會把執行緒的中斷狀態改變

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

4、中斷已經掛了的執行緒是無效的

     * <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

首先,啟動執行緒1、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

7、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 方法非常不穩定,

8、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

經典必讀

面試相關

前端

人工智能

設計模式

資料庫

資料結構與演算法

計算機基礎

一個優秀的廢人

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/262775.html

標籤:Java

上一篇:Java 在Excel中添加篩選器并執行篩選

下一篇:大廠面試通關指南,已拿騰訊,阿里offer(附100+最新大廠真題)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more