線程:Thread類源碼解析(上)

1.基本架構

Thread類涉及較多基本概念,理解基本概念是讀源碼的前提。

類註釋

  1. 每個線程都有優先級,高優先級的線程可能會優先執行
  2. 父線程創建子線程後,子線程的優先級、是否爲守護線程等屬性與父線程是一致的
  3. JVM啓動時,會將主線程執行,主線程是非守護線程。JVM在如下幾種情況下才會停止,否則將持續運行
    1)類運行時調用exit方法,且安全管理器已允許進行退出操作
    2)全部非守護線程都已停止運行,無論是正常執行完成或是運行出錯
  4. 每個線程都有名字,多個線程可以具有相同的名字。如果構造方法中沒有傳入指定名稱,則會自動生成

任務狀態

目前比較多的是如下兩個版本,

  1. 版本1
    image
    注意在RUNNABLE和RUNNING之間是雙向箭頭,當線程切換時,原本正在執行的線程從RUNNING狀態轉移至RUNNABLE狀態。

  2. 版本2(目前應用更多)
    image

任務狀態說明,

1)NEW

線程對象剛創建完成,但是還沒有運行。在new Thread()之後沒有調用start方法之前的狀態。

2)RUNNABLE

父線程調用子線程的start方法後,該線程對象纔在JVM中掛號(JVM知道該線程要搞事)。版本1中的解釋是此時處於可運行狀態,但是還沒有被CPU選中。版本2中則是調用start方法後,無論是否被CPU選中都稱爲RUNNABLE狀態。

3)RUNNING

只出現在版本1中,一旦被CPU選中,才能執行線程的run方法,此時纔算真正意義上的運行。版本2中RUNNING狀態的轉化

  1. 轉入RUNNABLE狀態
    當CPU切換到其他線程時,正在運行的線程狀態變爲RUNNABLE,因爲run方法沒有在被執行
  2. 轉入BLOCKED狀態
    正在執行的線程調用了wait或sleep方法時,就會進入BLOCKED狀態。區別是wait方法會放下當前CPU使用權,而sleep方法繼續佔用CPU
  3. 轉入TERMINATED狀態
    run方法執行完畢或意外終止。或者正在被執行的線程被調用了stop方法

4)BLOCKED

阻塞,由RUNNABLE或RUNNING進入BLOCKED的原因有,

  • 線程等待獲得 monitor lock 鎖,比如在等待進入 synchronized 修飾的代碼塊或方法時,會從 RUNNABLE 變成 BLOCKED
  • 正在執行的線程調用了wait或sleep方法後就會進入BLOCKED狀態(版本1中的解釋)。在版本2中,執行了wait方法後,會進入到WAITING或TIMED_WAITING狀態。

由BLOCKED進入RUNNABLE狀態的時機,

  • 阻塞操作結束,等待CPU再次選中的這段過程
  • 阻塞過程被打斷,如其他線程調用了interrupt方法
  • sleep方法設定的休眠時間結束後會回到RUNNABLE狀態
  • 由於wait操作進入BLOCKED狀態的線程,其他線程發出notify或notifyAll的信號時,會喚醒進入到RUNNABLE狀態

線程在BLOCKED狀態下如果被調用了stop方法時會轉入TERMINATED狀態。

5)TERMINATED

TERMINATED 狀態意味着線程的生命週期已經走完。這是線程的終止狀態。此狀態的線程不會再轉化爲其它任何狀態。

處於 RUNNING 或者 BLOCKED 狀態的線程都有可能變爲 TERMINATED 狀態,但原因是類似的,如下:

  1. 線程運行正常結束
  2. 線程運行異常終止
  3. JVM意外停止

6)WAITING和TIMED_WAITING

版本2中,遇到Object#waitThread#joinLockSupport#park這些方法時,線程會等待另一個線程完成特定動作之後才結束等待。二者的區別是TIMED_WAITING有設定等待時間。

注意,這幾種狀態並不是任務所有的狀態,只是在 Java 源碼中列舉出了幾種狀態, Java 線程的處理方法都是圍繞這幾種狀態的。
在這裏插入圖片描述

優先級

優先級代表線程執行的機會的大小,優先級高的可能先執行,低的可能後執行,在 Java 源碼中,優先級從低到高分別是 1 到 10,線程默認 new 出來的優先級都是 5,源碼如下,

// 最低優先級
public final static int MIN_PRIORITY = 1;

// 普通優先級,也是默認的
public final static int NORM_PRIORITY = 5;

// 最大優先級
public final static int MAX_PRIORITY = 10;

請注意優先級大僅僅是概率會更大,並不意味着就一定能夠先於優先級低的獲取。和搖車牌號一個道理,即使現在中籤概率是標準的數倍,但搖中依然搖搖無期。而身邊卻時不時的出現第一次搖號就中的人。如果在 CPU 比較空閒的時候,那麼優先級就沒有用了。

守護線程

默認創建的線程都是守護線程。

創建守護線程時,需要將 Thread 的 daemon 屬性設置成 true,守護線程的優先級很低,當 JVM 退出時,是不關心有無守護線程的,即使還有很多守護線程,JVM 仍然會退出。

守護線程類似於餐廳清潔員,一直在默默地做打掃衛生的工作。這個工作相對獨立,不需要和別的角色有什麼交互。而當其他所有人都不工作了,也就沒有工作的必要了,因爲不會有新的垃圾產生。那麼可以下班,餐廳也就關門了。

ClassLoader

ClassLoader可以簡單理解成類加載器,就是把類從文件、二進制數組、URL 等位置加載成可運行 Class。

2. 多線程實現的兩種方式

無返回值的線程初始化方式有兩種,

繼承Thread類及start方法源碼

class MyThread extends Thread{
  @Override
  public void run() {
    log.info(Thread.currentThread().getName());
  }
}
@Test
// 調用 start 方法即可,會自動調用到 run 方法的
public void extendThreadInit(){
  new MyThread().start();
}

start方法源碼如下,

public synchronized void start() {
    // 如果沒有初始化,拋異常
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    group.add(this);
    // started 是個標識符,動作發生之前標識符是 false,發生完成之後變成 true
    boolean started = false;
    try {
        // 這裏會創建一個新的線程,執行完成之後,新的線程已經在運行了,既 target 的內容已經在運行了
        start0();
        // 這裏執行的還是主線程
        started = true;
    } finally {
        try {
            // 如果失敗,把線程從線程組中刪除
            if (!started) {
                group.threadStartFailed(this);
            }
         // Throwable 可以捕捉一些 Exception 捕捉不到的異常,比如說子線程拋出的異常
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}
// 開啓新線程使用的是 native 方法
private native void start0();

start方法主要邏輯如下,

  1. 檢查線程狀態,判斷是否可啓動
  2. 將子線程加入到線程組中
  3. 調用start0方法

start方法並不調用run方法,而是調用start0方法。start0方法是native方法,也稱爲JNI(Java Native Interface)方法。JNI方法是java和其它語言交互的方式。同樣也是java代碼和虛擬機交互的方式,虛擬機就是由C++和彙編所編寫。

start0會進入JVM執行,run方法的執行在源碼中無法找到,但是在start方法的註釋中有這樣一句話,

the Java Virtual Machine* calls the run method of this thread.

所以run方法的調用過程如下,
image

實現Runnable及run方法源碼

Thread thread = new Thread(new Runnable() {
  @Override
  public void run() {
    log.info("{} begin run",Thread.currentThread().getName());
  }
});
// 開一個子線程去執行
thread.start();
// 不會新起線程,是在當前主線程上繼續運行
thread.run();

這種就是實現 Runnable 的接口,並作爲 Thread 構造器的入參。

調用時使用了兩種方式,可以根據情況選擇使用 start 或 run 方法,

  • 使用 start 會開啓子線程來執行 run 裏面的內容
  • 使用 run 方法執行的還是主線程

run方法源碼如下,

public void run() {
    if (target != null) {
        target.run();
    }
}

將Runnable對象傳入Thread類中,調用run方法的過程,
image

Thread和Runnable之間的關係

看若兄弟,實爲父子。Thread類的定義如下,

public class Thread implements Runnable

Thread實現了Runnable接口。

Thread類的run方法上有@Override註解。所以繼承thread類實現多線程,其實也相當於是實現runnable接口的run方法。此時,不需要再傳入一個Runnable類去啓動。它自己已具備了thread的功能,自己就可以運轉起來。

Thread類也實現了Runnable接口,那麼Thread子類對象也可以傳入另外的Thread對象,讓其執行自己的run方法。

3.初始化方法

源碼關鍵部分如下,

// 無參構造器,線程名字自動生成
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

// g 代表線程組,線程組可以對組內的線程進行批量的操作
// target 是Runnable對象
// name 線程的名字,自定義或自動生成
// stackSize 可以設置堆棧的大小
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name.toCharArray();
    // 創建Thread對象的線程作爲父線程
    Thread parent = currentThread();
    this.group = g;
    // 子線程會繼承父線程的守護屬性
    this.daemon = parent.isDaemon();
    // 子線程繼承父線程的優先級屬性
    this.priority = parent.getPriority();
    // classLoader
    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 的屬性值不爲空時
    // 會把 inheritableThreadLocals 裏面的值全部傳遞給子線程
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    this.stackSize = stackSize;
    /* Set thread ID */
    // 線程 id 自增
    tid = nextThreadID();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章