Thread類源碼解析
1.基本架構
Thread類涉及較多基本概念,理解基本概念是讀源碼的前提。
類註釋
- 每個線程都有優先級,高優先級的線程可能會優先執行
- 父線程創建子線程後,子線程的優先級、是否爲守護線程等屬性與父線程是一致的
- JVM啓動時,會將主線程執行,主線程是非守護線程。JVM在如下幾種情況下才會停止,否則將持續運行
1)類運行時調用exit方法,且安全管理器已允許進行退出操作
2)全部非守護線程都已停止運行,無論是正常執行完成或是運行出錯 - 每個線程都有名字,多個線程可以具有相同的名字。如果構造方法中沒有傳入指定名稱,則會自動生成
任務狀態
目前比較多的是如下兩個版本,
-
版本1
注意在RUNNABLE和RUNNING之間是雙向箭頭,當線程切換時,原本正在執行的線程從RUNNING狀態轉移至RUNNABLE狀態。 -
版本2(目前應用更多)
任務狀態說明,
1)NEW
線程對象剛創建完成,但是還沒有運行。在new Thread()
之後沒有調用start方法之前的狀態。
2)RUNNABLE
父線程調用子線程的start方法後,該線程對象纔在JVM中掛號(JVM知道該線程要搞事)。版本1中的解釋是此時處於可運行狀態,但是還沒有被CPU選中。版本2中則是調用start方法後,無論是否被CPU選中都稱爲RUNNABLE狀態。
3)RUNNING
只出現在版本1中,一旦被CPU選中,才能執行線程的run方法,此時纔算真正意義上的運行。版本2中RUNNING狀態的轉化
- 轉入RUNNABLE狀態
當CPU切換到其他線程時,正在運行的線程狀態變爲RUNNABLE,因爲run方法沒有在被執行 - 轉入BLOCKED狀態
正在執行的線程調用了wait或sleep方法時,就會進入BLOCKED狀態。區別是wait方法會放下當前CPU使用權,而sleep方法繼續佔用CPU - 轉入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 狀態,但原因是類似的,如下:
- 線程運行正常結束
- 線程運行異常終止
- JVM意外停止
6)WAITING和TIMED_WAITING
版本2中,遇到Object#wait
、Thread#join
、LockSupport#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方法主要邏輯如下,
- 檢查線程狀態,判斷是否可啓動
- 將子線程加入到線程組中
- 調用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方法的調用過程如下,
實現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方法的過程,
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();
}