JDK源碼那些事兒之淺析Thread上篇

JAVA中多線程的操做對於初學者而言是比較難理解的,其實聯想到底層操做系統時咱們可能會稍微明白些,對於程序而言最終都是硬件上運行二進制指令,然而,這些又太過底層,今天來看一下JAVA中的線程,淺析JDK源碼中的Thread類,以後能幫助咱們更好的處理線程問題java

前言

Thread思惟導圖

JDK版本號:1.8.0_171

在Thread註釋中能夠看到大佬對其進行的解釋:緩存

Thread就是程序中一個線程的執行.JVM容許一個應用中多個線程併發執行

每一個線程都有優先級.高優先級線程優先於低優先級線程執行
每一個線程均可以(不能夠)被標記爲守護線程
當線程中的run()方法代碼裏面又建立了一個新的線程對象時,新建立的線程優先級和父線程優先級同樣
當且僅當父線程爲守護線程時,新建立的線程纔會是守護線程安全

當JVM啓動時,一般會有惟一的一個非守護線程(這一線程用於調用指定類的main()方法)
JVM會持續執行線程直到下面某一個狀況發生爲止:
1.類運行時exit()方法被調用且安全機制容許此exit()方法的調用.
2.全部非守護類型的線程均已經終止,或者run()方法調用返回或者在run()方法外部拋出了一些可傳播性的異常.多線程

能夠聯想下JVM的啓動過程,從main方法啓動,能夠本身寫代碼查看下線程狀況併發

線程實現

Thread註釋類上清楚的寫明瞭線程的兩種實現方式:app

  • 定義一個繼承Thread類的子類,子類可覆寫父類的run()方法
  • 實現Runnable接口

示例以下,相信各位讀者應該很是熟悉了jvm

public static void main(String[] args) {
        // 1.繼承Thread類
        ExtendsThread extendsThread = new ExtendsThread("test1");
        extendsThread.start();
        // 2.實現Runnable接口
        ImplementsRunnable implementsRunnable = new ImplementsRunnable("test2");
        // 實現Runnable接口的類不能單獨使用,最終仍是要依賴Thread
        Thread implementsRunnableThread = new Thread(implementsRunnable);
        implementsRunnableThread.start();
    }

    static class ExtendsThread extends Thread{

        private String name;

        public ExtendsThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println("ExtendsThread is " + name);
        }
    }

    static class ImplementsRunnable implements Runnable{

        private String name;

        public ImplementsRunnable(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println("ImplementsRunnable is " + name);
        }
    }

從上面的實現中咱們能看到最終都須要實現Runnable接口,也就是實現run方法,Thread自己也是實現了Runnable接口(可參考源碼部分)ide

Runnable

先看下Runnable接口實現:函數

public interface Runnable {
    public abstract void run();
}

很是簡單,就一個run方法,也就意味着咱們只要將自定義代碼邏輯在run方法中實現便可,咱們在日常的線程實現中最簡單的使用方式也就是如上面線程實現的方式,因此咱們最終須要了解的部分仍是在於Thread,接下來就看看本文的重點Thread工具

類定義

public class Thread implements Runnable

Thread關係圖

初始化

先了解下static塊的部分,這裏在類首次加載時執行,主要做用就是將C/C++中的方法映射到Java中的native方法,實現方法命名的解耦

通俗點說就是咱們在以後調用native方法時,jvm會直接去調用本地系統方法完成操做,不會再去查找這個方法在哪,再去連接等等,至關於先進行註冊,以後就直接操做使用,由於涉及到底層c語言,這裏不過多深刻解釋

/* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

內部接口/類

Thread內部實現了一些接口和類,下面來一一進行說明

UncaughtExceptionHandler

未捕獲異常處理器,在線程因爲未捕獲的異常終止時,JVM會進行一些處理,處理流程以下:

  • JVM調用終止線程的getUncaughtExceptionHandler方法獲取終止線程的uncaughtExceptionHandler
  • 非null則調用uncaughtExceptionHandler的uncaughtException方法,同時將此終止線程和其異常做爲參數傳入
  • null則找到終止線程所在的最上級線程組,調用其uncaughtException方法,同時將此終止線程和其異常做爲參數傳入
  • 調用Thread.getDefaultUncaughtExceptionHandler獲取handle,非空則調用其uncaughtException方法,空則判斷調用e.printStackTrace(System.err)處理

整個調用處理過程可參考下列源碼部分:

// 內部異常處理接口
    public interface UncaughtExceptionHandler {

        void uncaughtException(Thread t, Throwable e);
    }
    
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        // 返回handle自己或線程組(實現了這個接口)
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }
    
    // ThreadGroup中代碼邏輯
    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                // 默認handle非空則調用
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                // 未設置基本都是這裏進行打印堆棧信息
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

可參考下列測試代碼理解:

public static void main(String[] args) {
        // 實現UncaughtExceptionHandler接口
        Thread.UncaughtExceptionHandler handler = (t, e) -> {
            System.out.println("test:" + t.getName());
            e.printStackTrace();
        };
        // 設置默認UncaughtExceptionHandler
        // Thread.setDefaultUncaughtExceptionHandler(handler);
        ExtendsThread extendsThread = new ExtendsThread("test");
        // 設置UncaughtExceptionHandler
        extendsThread.setUncaughtExceptionHandler(handler);
        extendsThread.start();
    }

    static class ExtendsThread extends Thread {

        private String name;

        public ExtendsThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            int a = 1 / 0;
            System.out.println(a);
        }
    }

從這個接口咱們能夠知道對於線程異常中止咱們實際上是能夠進行一些後續操做的,經過實現UncaughtExceptionHandler接口來進行線程異常後續處理操做,不過我還沒見過有什麼源碼中用過,通常都是直接catch中處理,這裏就先了解下便可

Caches/WeakClassKey

咱們能夠看到WeakClassKey這個內部類繼承了WeakReference,而WeakClassKey被Caches所使用,從名字咱們也能明白其部分含義,本地緩存,WeakClassKey是弱引用相關類,至於弱引用的使用你們能夠自行Google,這裏很少說,若是你看過《深刻理解Java虛擬機》,應該多少有點了解

subclassAudits提供了一個哈希表緩存,該緩存的鍵類型爲java.lang.Thread.WeakClassKey,注意看它的值類型是一個java.lang.Boolean類型的,從其代碼註釋能夠知道這個哈希表緩存中保存的是全部子類的代碼執行安全性檢測結果
  
subclassAuditsQueue定義了一個Queue隊列,保存已經審覈過的子類弱引用

/** cache of subclass security audit results */
    /* Replace with ConcurrentReferenceHashMap when/if it appears in a future
     * release */
    private static class Caches {
        /** cache of subclass security audit results */
        // 緩存安全檢查結果
        static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits =
            new ConcurrentHashMap<>();
        // 隊列
        /** queue for WeakReferences to audited subclasses */
        static final ReferenceQueue<Class<?>> subclassAuditsQueue =
            new ReferenceQueue<>();
    }

    /**
     *  Weak key for Class objects.
     **/
    static class WeakClassKey extends WeakReference<Class<?>> {
        /**
         * saved value of the referent's identity hash code, to maintain
         * a consistent hash code after the referent has been cleared
         */
        private final int hash;

        /**
         * Create a new WeakClassKey to the given object, registered
         * with a queue.
         */
        WeakClassKey(Class<?> cl, ReferenceQueue<Class<?>> refQueue) {
            super(cl, refQueue);
            hash = System.identityHashCode(cl);
        }

        /**
         * Returns the identity hash code of the original referent.
         */
        @Override
        public int hashCode() {
            return hash;
        }

        /**
         * Returns true if the given object is this identical
         * WeakClassKey instance, or, if this object's referent has not
         * been cleared, if the given object is another WeakClassKey
         * instance with the identical non-null referent as this one.
         */
        @Override
        public boolean equals(Object obj) {
            if (obj == this)
                return true;

            if (obj instanceof WeakClassKey) {
                Object referent = get();
                return (referent != null) &&
                       (referent == ((WeakClassKey) obj).get());
            } else {
                return false;
            }
        }
    }

在源碼中咱們能夠看到主要在isCCLOverridden中使用到了,這裏直接看下這個方法的源碼,註釋上也寫明瞭,驗證明例(多是子類)沒有違反安全約束能夠被構建,子類不能重寫安全相關的非final方法,不然須要檢查enableContextClassLoaderOverride運行權,主要是進行安全檢查的,簡單理解就好

/**
     * Verifies that this (possibly subclass) instance can be constructed
     * without violating security constraints: the subclass must not override
     * security-sensitive non-final methods, or else the
     * "enableContextClassLoaderOverride" RuntimePermission is checked.
     */
    private static boolean isCCLOverridden(Class<?> cl) {
        if (cl == Thread.class)
            return false;

        processQueue(Caches.subclassAuditsQueue, Caches.subclassAudits);
        // 生成key
        WeakClassKey key = new WeakClassKey(cl, Caches.subclassAuditsQueue);
        // 從緩存查找
        Boolean result = Caches.subclassAudits.get(key);
        if (result == null) {
            result = Boolean.valueOf(auditSubclass(cl));
            Caches.subclassAudits.putIfAbsent(key, result);
        }
        // 返回結果
        return result.booleanValue();
    }

State

線程狀態枚舉類,代表線程處於生命週期中的哪一個階段,線程在任意時刻只會處於下列其中一種狀態,只反映JVM中的線程狀態,不是反映操做系統線程狀態

public enum State {
        /**
         * 初始態
         * 線程建立完畢還未啓動,未調用start方法
         */
        NEW,

        /**
         * 包含兩種狀態
         * 1.就緒態
         * 2.運行態
         */
        RUNNABLE,

        /**
         * 阻塞態
         * synchronized會致使線程進入blocked狀態
         */
        BLOCKED,

        /**
         * 
         * 等待態
         * 3種狀況調用後會致使線程處於這個狀態:
         * 1.Object.wait
         * 2.Thread.join
         * 3.LockSupport.park
         * 
         * 等待態的線程會等待其餘線程執行特定的操做
         * 
         * 例如一個線程調用了Object.wait以後進入等待態,另外一個線程調用Object.notify或Object.notifyAll能夠將其喚醒,被喚醒的線程須要獲取對象的鎖才能恢復執行
         * 調用Thread.join會等待指定的線程終止
         */
        WAITING,

        /**
         * 
         * 超時等待態
         * 線程等待指定的時間再執行
         * 5種狀況調用後會致使線程處於這個狀態:
         * 1.Thread.sleep
         * 2.Object.wait 傳入等待時長
         * 3.Thread.join 傳入等待時長
         * 4.LockSupport.parkNanos
         * 5.LockSupport.parkUntil
         */
        TIMED_WAITING,

        /**
         * 終止態
         * 線程執行完畢
         */
        TERMINATED;
    }

線程狀態轉化

線程狀態轉化可參考下圖理解

線程狀態轉化圖

在Java中線程分爲6種狀態,上面枚舉類也已經說明了,這裏稍微詳細點說明下每種狀態的含義:

初始態(NEW)

  • 建立一個Thread對象,但還未調用start()啓動線程時,線程處於初始態

運行態(RUNNABLE)

運行態在Java中包括就緒態和運行態

1.就緒態:

  • 該狀態下的線程已經得到執行所需的全部資源,只要CPU分配執行權就能運行
  • 全部就緒態的線程存放在就緒隊列中

2.運行態:

  • 得到CPU執行權,正在執行的線程
  • 因爲一個CPU同一時刻只能執行一條線程,所以每一個CPU每一個時刻只有一個運行態的線程

阻塞態(BLOCKED)

  • 當一條正在執行的線程請求某一資源失敗時,就會進入阻塞態
  • 而在Java中,阻塞態專指請求鎖失敗時進入的狀態
  • 由一個阻塞隊列存放全部阻塞態的線程。處於阻塞態的線程會不斷請求資源,一旦請求成功,就會進入就緒隊列,等待執行

等待態(WAITING)

  • 當前線程中調用wait、join、park函數時,當前線程就會進入等待態
  • 也有一個等待隊列存放全部等待態的線程
  • 線程處於等待態表示它須要等待其餘線程的指示才能繼續運行
  • 進入等待態的線程會釋放CPU執行權,並釋放資源(如:鎖)

超時等待態(TIMED_WAITING)

  • 當運行中的線程調用sleep(time)、wait、join、parkNanos、parkUntil時,就會進入該狀態
  • 它和等待態同樣,並非由於請求不到資源,而是主動進入,而且進入後被其餘線程喚醒或超時自動喚醒
  • 進入該狀態後釋放CPU執行權和佔有的資源,其中wait()方法會釋放CPU執行權和佔有的鎖,sleep(long)方法僅釋放CPU使用權,鎖仍然佔用
  • 與等待態的區別:到了超時時間後自動進入阻塞隊列,開始競爭鎖

終止態(TERMINATED)

  • 線程執行結束後的狀態

其中有幾點須要注意的:

  • yield方法僅釋放CPU執行權,鎖仍然佔用,線程會被放入就緒隊列,會在短期內再次執行
  • wait和notify必須配套使用,即必須使用同一把鎖調用
  • wait和notify必須放在一個同步塊中調用,wait和notify的對象必須是他們所處同步塊的鎖對象

總結

以上對Thread進行了簡單的介紹說明,對於Thread的狀態轉換須要多理解理解,寫些代碼debug或者經過jdk工具觀察下jvm的線程狀態仍是頗有必要的

以上內容若有問題歡迎指出,筆者驗證後將及時修正,謝謝

參考

相關文章
相關標籤/搜索