Java
中的線程是使用Thread
類實現的,Thread
在初學Java
的時候就學過了,也在實踐中用過,不過一直沒從源碼的角度去看過它的實現,今天從源碼的角度出發,再次學習Java Thread
,願此後對Thread
的實踐更加駕輕就熟。java
相信閱讀過JDK
源碼的同窗都能感覺到JDK
源碼中有很是詳盡的註釋,閱讀某個類的源碼應當先看看註釋對它的介紹,註釋原文就不貼了,如下是我對它的總結:程序員
Thread
是程序中執行的線程,Java
虛擬機容許應用程序同時容許多個執行線程安全
每一個線程都有優先級的概念,具備較高優先級的線程優先於優先級較低的線程執行less
每一個線程均可以被設置爲守護線程ide
當在某個線程中運行的代碼建立一個新的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
的單繼承限制,一般使用這種方式建立線程更加靈活this
//定義線程
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();
複製代碼
建立線程時能夠給線程指定名字,若是沒有指定,會自動爲它生成名字spa
除非另有說明,不然將null
參數傳遞給Thread
類中的構造函數或方法將致使拋出 NullPointerException
閱讀一個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
實例是怎麼構造的?先預覽下它大體有多少個構造方法:
查看每一個構造方法內部源碼,發現均調用的是名爲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;
}
複製代碼
synchronized
同步塊,就會進入此狀態,此時線程暫停執行,直到得到請求的鎖這是一個靜態的本地方法,使當前執行的線程休眠暫停執行 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. * * @param millis * the length of time to sleep in milliseconds * * @throws IllegalArgumentException * if the value of {@code millis} is negative * * @throws InterruptedException * 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 * @exception IllegalArgumentException If the priority is not in the * range <code>MIN_PRIORITY</code> to * <code>MAX_PRIORITY</code>. * @exception SecurityException if the current thread cannot modify * this thread. * @see #getPriority * @see #checkAccess() * @see #getThreadGroup() * @see #MAX_PRIORITY * @see #MIN_PRIORITY * @see ThreadGroup#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. * * @throws SecurityException * 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
作出更多總結記錄。
因爲篇幅較長,暫且先記錄這些吧,後續會不按期更新原創文章,歡迎關注公衆號 「張少林同窗」!