從原始碼的角度再學「Thread」

前言
Java
中的執行緒是使用 Thread
類實現的, Thread
在初學 Java
的時候就學過了,也在實踐中用過,不過一直沒從原始碼的角度去看過它的實現,今天從原始碼的角度出發,再次學習 Java Thread
,願此後對 Thread
的實踐更加得心應手。
從註釋開始
相信閱讀過 JDK
原始碼的同學都能感受到 JDK
原始碼中有非常詳盡的註釋,閱讀某個類的原始碼應當先看看註釋對它的介紹,註釋原文就不貼了,以下是我對它的總結:
-
Thread
是程式中執行的執行緒,Java
虛擬機器允許應用程式同時允許多個執行執行緒 -
每個執行緒都有優先順序的概念,具有較高優先順序的執行緒優先於優先順序較低的執行緒執行
-
每個執行緒都可以被設定為守護執行緒
-
當在某個執行緒中執行的程式碼建立一個新的
Thread
物件時,新的執行緒優先順序跟建立執行緒一致 -
當
Java
虛擬機器啟動的時候都會啟動一個叫做main
的執行緒,它沒有守護執行緒,main
執行緒會繼續執行,直到以下情況傳送-
Runtime
類的退出方法exit
被呼叫並且安全管理器允許進行退出操作 - 所有非守護執行緒均已死亡,或者
run
方法執行結束正常返回結果,或者run
方法丟擲異常
-
-
建立執行緒第一種方式:繼承
Thread
類,重寫run
方法//定義執行緒類 class PrimeThread extends Thread { long minPrime; PrimeThread(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } //啟動執行緒 PrimeThread p = new PrimeThread(143); p.start(); 複製程式碼
-
建立執行緒第二種方式:實現
Runnable
介面,重寫run
方法,因為Java
的單繼承限制,通常使用這種方式建立執行緒更加靈活//定義執行緒 class PrimeRun implements Runnable { long minPrime; PrimeRun(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } //啟動執行緒 PrimeRun p = new PrimeRun(143); new Thread(p).start(); 複製程式碼
-
建立執行緒時可以給執行緒指定名字,如果沒有指定,會自動為它生成名字
-
除非另有說明,否則將
null
引數傳遞給Thread
類中的建構函式或方法將導致丟擲NullPointerException
Thread 常用屬性
閱讀一個 Java
類,先從它擁有哪些屬性入手:
//執行緒名稱,建立執行緒時可以指定執行緒的名稱 private volatile String name; //執行緒優先順序,可以設定執行緒的優先順序 private int priority; //可以配置執行緒是否為守護執行緒,預設為false private boolean daemon = false; //最終執行執行緒任務的`Runnable` private Runnable target; //描述執行緒組的類 private ThreadGroup group; //此執行緒的上下文ClassLoader private ClassLoader contextClassLoader; //所有初始化執行緒的數目,用於自動編號匿名執行緒,當沒有指定執行緒名稱時,會自動為其編號 private static int threadInitNumber; //此執行緒請求的堆疊大小,如果建立者沒有指定堆疊大小,則為0。, 虛擬機器可以用這個數字做任何喜歡的事情。, 一些虛擬機器會忽略它。 private long stackSize; //執行緒id private long tid; //用於生成執行緒ID private static long threadSeqNumber; //執行緒狀態 private volatile int threadStatus = 0; //執行緒可以擁有的最低優先順序 public final static int MIN_PRIORITY = 1; //分配給執行緒的預設優先順序。 public final static int NORM_PRIORITY = 5; //執行緒可以擁有的最大優先順序 public final static int MAX_PRIORITY = 10; 複製程式碼
所有的屬性命名都很語義化,其實已看名稱基本就猜到它是幹嘛的了,難度不大~~
Thread 構造方法
瞭解了屬性之後,看看 Thread
例項是怎麼構造的?先預覽下它大致有多少個構造方法:

檢視每個構造方法內部原始碼,發現均呼叫的是名為 init
的私有方法,再看 init
方法有兩個過載,而其核心方法如下:
/** * Initializes a Thread. * * @param g執行緒組 * @param target最終執行任務的 `run()` 方法的物件 * @param name新執行緒的名稱 * @param stackSize新執行緒所需的堆疊大小,或者 0 表示要忽略此引數 * @param acc要繼承的AccessControlContext,如果為null,則為 AccessController.getContext() * @param inheritThreadLocals 如果為 true,從構造執行緒繼承可繼承的執行緒區域性的初始值 */ 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) { //獲取SecurityManager中的執行緒組 g = security.getThreadGroup(); } //如果獲取的執行緒組還是為空 if (g == null) { //則使用父執行緒的執行緒組 g = parent.getThreadGroup(); } } //檢查安全許可權 g.checkAccess(); //使用安全管理器檢查是否有許可權 if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } //執行緒組中標記未啟動的執行緒數+1,這裡方法是同步的,防止出現執行緒安全問題 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; //初始化當前執行緒物件的執行緒ID,該方法是同步的,內部實際上是threadSeqNumber++ tid = nextThreadID(); } 複製程式碼
另一個過載 init
私有方法如下,實際上內部呼叫的是上述 init
方法:
private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } 複製程式碼
接下來看看所有構造方法:
-
空構造方法
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } 複製程式碼
內部呼叫的是
init
第二個過載方法,引數基本都是預設值,執行緒名稱寫死為"Thread-" + nextThreadNum()
格式,nextThreadNum()
為一個同步方法,內部維護一個靜態屬性表示執行緒的初始化數量+1:private static int threadInitNumber; private static synchronized int nextThreadNum() { return threadInitNumber++; } 複製程式碼
-
自定義執行任務
Runnable
物件的構造方法public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } 複製程式碼
與第一個構造方法區別在於可以自定義
Runnable
物件 -
自定義執行任務
Runnable
物件和AccessControlContext
物件的構造方法Thread(Runnable target, AccessControlContext acc) { init(null, target, "Thread-" + nextThreadNum(), 0, acc, false); } 複製程式碼
-
自定義執行緒組
ThreadGroup
和執行任務Runnable
物件的構造方法public Thread(ThreadGroup group, Runnable target) { init(group, target, "Thread-" + nextThreadNum(), 0); } 複製程式碼
-
自定義執行緒名稱
name
的構造方法public Thread(String name) { init(null, null, name, 0); } 複製程式碼
-
自定義執行緒組
ThreadGroup
和執行緒名稱name
的構造方法public Thread(ThreadGroup group, String name) { init(group, null, name, 0); } 複製程式碼
-
自定義執行任務
Runnable
物件和執行緒名稱name
的構造方法public Thread(Runnable target, String name) { init(null, target, name, 0); } 複製程式碼
-
自定義執行緒組
ThreadGroup
和執行緒名稱name
和執行任務Runnable
物件的構造方法public Thread(ThreadGroup group, Runnable target, String name) { init(group, target, name, 0); } 複製程式碼
-
全部屬性都是自定義的構造方法
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) { init(group, target, name, stackSize); } 複製程式碼
Thread
提供了非常靈活的過載構造方法,方便開發者自定義各種引數的 Thread
物件。
常用方法
這裡記錄一些比較常見的方法吧,對於 Thread
中存在的一些本地方法,我們暫且不用管它~
設定執行緒名稱
設定執行緒名稱,該方法為同步方法,為了防止出現執行緒安全問題,可以手動呼叫 Thread
的例項方法設定名稱,也可以在構造 Thread
時在構造方法中傳入執行緒名稱,我們通常都是在構造引數時設定
public final synchronized void setName(String name) { //檢查安全許可權 checkAccess(); //如果形參為空,丟擲空指標異常 if (name == null) { throw new NullPointerException("name cannot be null"); } //給當前執行緒物件設定名稱 this.name = name; if (threadStatus != 0) { setNativeName(name); } } 複製程式碼
獲取執行緒名稱
內部直接返回當前執行緒物件的名稱屬性
public final String getName() { return name; } 複製程式碼
啟動執行緒
public synchronized void start() { //如果不是剛建立的執行緒,丟擲異常 if (threadStatus != 0) throw new IllegalThreadStateException(); //通知執行緒組,當前執行緒即將啟動,執行緒組當前啟動執行緒數+1,未啟動執行緒數-1 group.add(this); //啟動標識 boolean started = false; try { //直接呼叫本地方法啟動執行緒 start0(); //設定啟動標識為啟動成功 started = true; } finally { try { //如果啟動呢失敗 if (!started) { //執行緒組內部移除當前啟動的執行緒數量-1,同時啟動失敗的執行緒數量+1 group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } 複製程式碼
我們正常的啟動執行緒都是呼叫 Thread
的 start()
方法,然後 Java
虛擬機器內部會去呼叫 Thred
的 run
方法,可以看到 Thread
類也是實現 Runnable
介面,重寫了 run
方法的:
@Override public void run() { //當前執行任務的Runnable物件不為空,則呼叫其run方法 if (target != null) { target.run(); } } 複製程式碼
Thread
的兩種使用方式:
- 繼承
Thread
類,重寫run
方法,那麼此時是直接執行run
方法的邏輯,不會使用target.run();
- 實現
Runnable
介面,重寫run
方法,因為Java
的單繼承限制,通常使用這種方式建立執行緒更加靈活,這裡真正的執行邏輯就會交給自定義Runnable
去實現
設定守護執行緒
本質操作是設定 daemon
屬性
public final void setDaemon(boolean on) { //檢查是否有安全許可權 checkAccess(); //本地方法,測試此執行緒是否存活。, 如果一個執行緒已經啟動並且尚未死亡,則該執行緒處於活動狀態 if (isAlive()) { //如果執行緒先啟動後再設定守護執行緒,將丟擲異常 throw new IllegalThreadStateException(); } //設定當前守護執行緒屬性 daemon = on; } 複製程式碼
判斷執行緒是否為守護執行緒
public final boolean isDaemon() { //直接返回當前物件的守護執行緒屬性 return daemon; } 複製程式碼
執行緒狀態
先來個執行緒狀態圖:

獲取執行緒狀態:
public State getState() { //由虛擬機器實現,獲取當前執行緒的狀態 return sun.misc.VM.toThreadState(threadStatus); } 複製程式碼
執行緒狀態主要由內部列舉類 State
組成:
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; } 複製程式碼
- NEW:剛剛建立,尚未啟動的執行緒處於此狀態
- RUNNABLE:在Java虛擬機器中執行的執行緒處於此狀態
- BLOCKED:被阻塞等待監視器鎖的執行緒處於此狀態,比如執行緒在執行過程中遇到
synchronized
同步塊,就會進入此狀態,此時執行緒暫停執行,直到獲得請求的鎖 - WAITING:無限期等待另一個執行緒執行特定操作的執行緒處於此狀態
- 通過 wait() 方法等待的執行緒在等待 notify() 方法
- 通過 join() 方法等待的執行緒則會等待目標執行緒的終止
- TIMED_WAITING:正在等待另一個執行緒執行動作,直到指定等待時間的執行緒處於此狀態
- 通過 wait() 方法,攜帶超時時間,等待的執行緒在等待 notify() 方法
- 通過 join() 方法,攜帶超時時間,等待的執行緒則會等待目標執行緒的終止
- TERMINATED:已退出的執行緒處於此狀態,此時執行緒無法再回到 RUNNABLE 狀態
執行緒休眠
這是一個靜態的本地方法,使當前執行的執行緒休眠暫停執行 millis
毫秒,當休眠被中斷時會丟擲 InterruptedException
中斷異常
/** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds, subject to * the precision and accuracy of system timers and schedulers. The thread * does not lose ownership of any monitors. * * @parammillis *the length of time to sleep in milliseconds * * @throwsIllegalArgumentException *if the value of {@code millis} is negative * * @throwsInterruptedException *if any thread has interrupted the current thread. The *<i>interrupted status</i> of the current thread is *cleared when this exception is thrown. */ public static native void sleep(long millis) throws InterruptedException; 複製程式碼
檢查執行緒是否存活
本地方法,測試此執行緒是否存活。 如果一個執行緒已經啟動並且尚未死亡,則該執行緒處於活動狀態。
/** * Tests if this thread is alive. A thread is alive if it has * been started and has not yet died. * * @return<code>true</code> if this thread is alive; *<code>false</code> otherwise. */ public final native boolean isAlive(); 複製程式碼
執行緒優先順序
- 設定執行緒優先順序
/** * Changes the priority of this thread. * <p> * First the <code>checkAccess</code> method of this thread is called * with no arguments. This may result in throwing a * <code>SecurityException</code>. * <p> * Otherwise, the priority of this thread is set to the smaller of * the specified <code>newPriority</code> and the maximum permitted * priority of the thread's thread group. * * @param newPriority priority to set this thread to * @exceptionIllegalArgumentExceptionIf the priority is not in the *range <code>MIN_PRIORITY</code> to *<code>MAX_PRIORITY</code>. * @exceptionSecurityExceptionif the current thread cannot modify *this thread. * @see#getPriority * @see#checkAccess() * @see#getThreadGroup() * @see#MAX_PRIORITY * @see#MIN_PRIORITY * @seeThreadGroup#getMaxPriority() */ public final void setPriority(int newPriority) { //執行緒組 ThreadGroup g; //檢查安全許可權 checkAccess(); //檢查優先順序形參範圍 if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { //如果優先順序形參大於執行緒組最大執行緒最大優先順序 if (newPriority > g.getMaxPriority()) { //則使用執行緒組的優先順序資料 newPriority = g.getMaxPriority(); } //呼叫本地設定執行緒優先順序方法 setPriority0(priority = newPriority); } } 複製程式碼
執行緒中斷
有一個 stop()
例項方法可以強制終止執行緒,不過這個方法因為太過於暴力,已經被標記為過時方法,不建議程式設計師再使用,因為 強制終止執行緒 會導致資料不一致的問題。
這裡關於執行緒中斷的方法涉及三個:
//例項方法,通知執行緒中斷,設定標誌位 public void interrupt(){} //靜態方法,檢查當前執行緒的中斷狀態,同時會清除當前執行緒的中斷標誌位狀態 public static boolean interrupted(){} //例項方法,檢查當前執行緒是否被中斷,其實是檢查中斷標誌位 public boolean isInterrupted(){} 複製程式碼
interrupt() 方法解析
/** * Interrupts this thread. * * <p> Unless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. * * <p> If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. * * <p> If this thread is blocked in an I/O operation upon an {@link * java.nio.channels.InterruptibleChannel InterruptibleChannel} * then the channel will be closed, the thread's interrupt * status will be set, and the thread will receive a {@link * java.nio.channels.ClosedByInterruptException}. * * <p> If this thread is blocked in a {@link java.nio.channels.Selector} * then the thread's interrupt status will be set and it will return * immediately from the selection operation, possibly with a non-zero * value, just as if the selector's {@link * java.nio.channels.Selector#wakeup wakeup} method were invoked. * * <p> If none of the previous conditions hold then this thread's interrupt * status will be set. </p> * * <p> Interrupting a thread that is not alive need not have any effect. * * @throwsSecurityException *if the current thread cannot modify this thread * * @revised 6.0 * @spec JSR-51 */ public void interrupt() { //檢查是否是自身呼叫 if (this != Thread.currentThread()) //檢查安全許可權,這可能導致丟擲{@link * SecurityException}。 checkAccess(); //同步程式碼塊 synchronized (blockerLock) { Interruptible b = blocker; //檢查是否是阻塞執行緒呼叫 if (b != null) { //設定執行緒中斷標誌位 interrupt0(); //此時丟擲異常,將中斷標誌位設定為false,此時我們正常會捕獲該異常,重新設定中斷標誌位 b.interrupt(this); return; } } //如無意外,則正常設定中斷標誌位 interrupt0(); } 複製程式碼
- 執行緒中斷方法不會使執行緒立即退出,而是給執行緒傳送一個通知,告知目標執行緒,有人希望你退出啦~
- 只能由自身呼叫,否則可能會丟擲
SecurityException
- 呼叫中斷方法是由目標執行緒自己決定是否中斷,而如果同時呼叫了
wait
,join
,sleep
等方法,會使當前執行緒進入阻塞狀態,此時有可能發生InterruptedException
異常 - 被阻塞的執行緒再呼叫中斷方法是不合理的
- 中斷不活動的執行緒不會產生任何影響
檢查執行緒是否被中斷:
/** * Tests whether this thread has been interrupted.The <i>interrupted * status</i> of the thread is unaffected by this method. 測試此執行緒是否已被中斷。, 執行緒的<i>中斷*狀態</ i>不受此方法的影響。 * * <p>A thread interruption ignored because a thread was not alive * at the time of the interrupt will be reflected by this method * returning false. * * @return<code>true</code> if this thread has been interrupted; *<code>false</code> otherwise. * @see#interrupted() * @revised 6.0 */ public boolean isInterrupted() { return isInterrupted(false); } 複製程式碼
靜態方法,會清空當前執行緒的中斷標誌位:
/** *測試當前執行緒是否已被中斷。, 此方法清除執行緒的* <i>中斷狀態</ i>。, 換句話說,如果要連續兩次呼叫此方法,則* second呼叫將返回false(除非當前執行緒再次被中斷,在第一次呼叫已清除其中斷的*狀態之後且在第二次呼叫已檢查之前), 它) * * <p>A thread interruption ignored because a thread was not alive * at the time of the interrupt will be reflected by this method * returning false. * * @return<code>true</code> if the current thread has been interrupted; *<code>false</code> otherwise. * @see #isInterrupted() * @revised 6.0 */ public static boolean interrupted() { return currentThread().isInterrupted(true); } 複製程式碼
總結
記錄自己閱讀 Thread
類原始碼的一些思考,不過對於其中用到的很多本地方法只能望而卻步,還有一些程式碼沒有看明白,暫且先這樣吧,如果有不足之處,請留言告知我,謝謝!後續會在實踐中對 Thread
做出更多總結記錄。
最後
由於篇幅較長,暫且先記錄這些吧,後續會不定期更新原創文章,歡迎關注公眾號 「張少林同學」!
