JUC原始碼講解:逐步解析 join()

acdongla發表於2024-03-13

JUC原始碼講解:逐步解析 join()

問題丟擲

join() 在原始碼中其實是使用了 wait() 方法的, 因此, wait() 的特性 join() 都能繼承過來, 我們知道wait()有什麼特性呢?

wait()的特性:

  • 會釋放鎖
  • 對中斷異常敏感
  • 會釋放CPU時間片

我也對wait()做了講解,想了解可移步 https://www.cnblogs.com/acdongla/p/18071381

可是, join() 真的會釋放鎖嗎? 為什麼一些例子中, 使用join() 後其他執行緒仍然被阻塞住了?join() 還有什麼需要注意的特性嗎? 讓我們帶著問題尋找答案吧

解析原始碼

進入 join() 看到這樣一段程式碼

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) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

先看這段程式碼:

if (millis < 0) {
    throw new IllegalArgumentException("timeout value is negative");
}

可以看到, 傳遞的時間不能是負數

一個重點來了, alive() 函式出現了:

if (millis == 0) {
    while (isAlive()) {
        wait(0);
    }

isAlive() 是對執行緒狀態的檢查, 必須是start()後的執行緒才會滿足條件.

因此,因為有 isAlive() 的檢查 , join()函式只能線上程 start() 後使用

注意!!! 這裡的 wait(0)

我們知道, wait()能釋放鎖資源, 可我們需要注意一個問題, 這裡的 wait() 釋放的是誰的鎖資源?

答: **釋放的是當前執行緒的鎖資源, 這裡的wait()是 currentThread.wait(), 是 this.wait() **

我們寫一段程式碼來說明這個問題

public class Main {

    @SneakyThrows
    public static void main(String[] args) {
        new Thread(new YYThread(), "YY-1").start();
        Thread.sleep(100);
        new Thread(new YYThread(), "YY-2").start();
    }

    static class YYThread implements Runnable {

        @SneakyThrows
        @Override
        public void run() {
            /// 注意!!! 這裡使用鎖物件是 Main.class
            synchronized (Main.clsss) {
                System.out.println(Thread.currentThread().getName() + "啟動了");

                Thread.currentThread().join();

            }
        }
    }
}

注意, 在sync中使用的鎖物件是 Main.class

執行這段程式碼,會發現執行緒 "YY-2" 被阻塞了

為什麼, join()不是同wait()一樣,會釋放鎖資源嗎?

但!!! wait()釋放的是當前執行緒的鎖資源, 因此,使用全域性變數來作為鎖是不可行的

我們把run()函式做這樣的修改,就可以不阻塞了

public void run() {
    /// 修改了鎖, 修改為了當前執行緒
    synchronized (Thread.currentThread()) {
        System.out.println(Thread.currentThread().getName() + "啟動了");

        Thread.currentThread().join();

    }
}

修改後,從輸出上看,兩個執行緒都正常執行了

總結

  • join() 完全繼承了 wait() 方法的特性, 但需要注意的事, join() 中的原始碼使用了wait(), 代表的事 currentThread.wait(), 是"當前執行緒.wait()",因此釋放的只是當前執行緒的鎖資源
  • 因為原始碼中對isAlive() 的判斷, join() 只能線上程 start() 之後才能使用
  • join() 的引數不能是負數

相關文章