JUC原始碼講解:逐步解析 Thread.init() 原始碼

acdongla發表於2024-03-12

# JUC原始碼講解:逐步解析 Thread.init() 原始碼

丟擲問題

我們在 new Thread() 時,init() 方法便會自動呼叫,用來建立這個執行緒。那麼,建立執行緒時都發生了什麼事?子執行緒與父執行緒有何關係?執行緒是怎麼建立的?juc怎麼選擇 ThreadGroup? 讓我們從原始碼中尋找答案吧!

檢視原始碼

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

可以看到,init() 傳遞了 ThreadGroup(劃重點),以及 name(執行緒名),然後呼叫了重名方法 init()。

為了方便大家學習,我先把這個init()的程式碼貼出來,然後帶領大家逐步分析原始碼

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;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            
            if (security != null) {
                g = security.getThreadGroup();
            }

            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        g.checkAccess();
    
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

        this.stackSize = stackSize;

        tid = nextThreadID();
    }

我們先看第一段程式碼:

if (name == null) {
    throw new NullPointerException("name cannot be null");
}

this.name = name;

每一個執行緒都必須要有一個名字,建立時會自動起名,這裡進行了反覆的檢查。

建議大家在寫專案的時候都為執行緒自定義執行緒名,以好使用 stack log 查日誌

再看下一段:

Thread parent = currentThread();

這裡是找到父執行緒,簡單地說,如果我們是在 main 方法中呼叫的 new Thread(), 那麼 main 執行緒就是這個 parent

接著看下一段程式碼,這段程式碼是保證了 ThreadGroup 的可用

// 先找到系統的 ThreadGroup
SecurityManager security = System.getSecurityManager();

// 尊重使用者傳遞的 ThreadGroup
if (g == null) {

    if (security != null) {
        // 使用者沒有傳遞,就有限使用系統的 ThreadGroup
        g = security.getThreadGroup();
    }

    if (g == null) {
        // 連繫統的都找不到,就使用父執行緒的 ThreadGroup
        g = parent.getThreadGroup();
    }
}

這裡,是程式碼對 ThreadGroup 的反覆確認,如果 ThreadGroup 為空,並且我們找到了系統級別的 ThreadGroup,那就使用系統級別的 ThreadGroup,如果系統級的 ThreadGroup 也沒有找到,就使用父執行緒的ThreadGroup。

這裡可以看到優先順序了,juc 尊重使用者的 ThreadGroup,次選System的,最後才選用 parent 的

看下一段,檢查執行緒可用,不必多說

g.checkAccess();

看下一段,這裡的執行緒還不是開啟的狀態,NEW 狀態的執行緒!,還沒有 start,新增到 ThreadGroup中

g.addUnstarted(); // NEW

接著,我們看這一段核心的程式碼:

this.daemon = parent.isDaemon(); // 繼承父執行緒的守護執行緒
this.priority = parent.getPriority(); // 繼承父執行緒的優先順序

// 擊沉父執行緒的 contextClassLoader
if (security == null || isCCLOverridden(parent.getClass()))
    this.contextClassLoader = parent.getContextClassLoader();
else
    this.contextClassLoader = parent.contextClassLoader;

this.inheritedAccessControlContext =
    acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);

// 儘可能的繼承父執行緒的 inheritableThreadLocals
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals =
    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        

子執行緒與父執行緒的關係在這段程式碼中昭然若揭,子執行緒會繼承父執行緒的 daemon 、 priority 、contextClassLoader,會盡可能的繼承父執行緒的 inheritableThreadLocals

最後一段程式碼,設定執行緒編號,收尾!

tid = nextThreadID();

我們點進去這個函式看一看:

private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
}

看到了嗎,是sync的,juc 是這樣保證執行緒編號的一致性的

總結

透過觀察 Thread.init() 的原始碼,我們可以主要了解到:

  • ThreadGroup:JUC會尊重使用者指定的ThreadGroup,次選 System 的,最後會選擇父執行緒的
  • 子執行緒與父執行緒的繼承關係:子執行緒會完全繼承父執行緒的 daemon 、 priority 、contextClassLoader,會盡可能的繼承父執行緒的 inheritableThreadLocals
  • Thread.init() 時的執行緒狀態是 NEW,會被新增到 ThreadGroup 中
  • init() 時透過 sync 保證執行緒編號的不重複

相關文章