摘要:前言中的線程是使用類實現的,在初學的時候就學過了,也在實踐中用過,不過一直沒從源碼的角度去看過它的實現,今天從源碼的角度出發,再次學習,愿此后對的實踐更加得心應手。如果一個線程已經啟動并且尚未死亡,則該線程處于活動狀態。
前言
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. * * @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 * interrupted status 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線程優先級true
if this thread is alive; *false
otherwise. */ public final native boolean isAlive();
設置線程優先級
/** * Changes the priority of this thread. *線程中斷* First the
checkAccess
method of this thread is called * with no arguments. This may result in throwing a *SecurityException
. ** Otherwise, the priority of this thread is set to the smaller of * the specified
newPriority
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 * rangeMIN_PRIORITY
to *MAX_PRIORITY
. * @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. * *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. * *
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}. * *
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}. * *
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. * *
If none of the previous conditions hold then this thread"s interrupt * status will be set.
* *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 interrupted * status of the thread is unaffected by this method. 測試此線程是否已被中斷。, 線程的中斷*狀態 i>不受此方法的影響。 * *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
true
if this thread has been interrupted; *false
otherwise. * @see #interrupted() * @revised 6.0 */ public boolean isInterrupted() { return isInterrupted(false); }
靜態方法,會清空當前線程的中斷標志位:
/** *測試當前線程是否已被中斷。, 此方法清除線程的* 中斷狀態 i>。, 換句話說,如果要連續兩次調用此方法,則* second調用將返回false(除非當前線程再次被中斷,在第一次調用已清除其中斷的*狀態 之后且在第二次調用已檢查之前), 它) * *總結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
true
if the current thread has been interrupted; *false
otherwise. * @see #isInterrupted() * @revised 6.0 */ public static boolean interrupted() { return currentThread().isInterrupted(true); }
記錄自己閱讀Thread類源碼的一些思考,不過對于其中用到的很多本地方法只能望而卻步,還有一些代碼沒有看明白,暫且先這樣吧,如果有不足之處,請留言告知我,謝謝!后續會在實踐中對Thread做出更多總結記錄。
最后由于篇幅較長,暫且先記錄這些吧,后續會不定期更新原創文章,歡迎關注公眾號 「張少林同學」!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73112.html
摘要:我們繼續看代碼的意思是這個是一段內嵌匯編代碼。也就是在語言中使用匯編代碼。就是匯編版的比較并交換。就是保證在多線程情況下,不阻塞線程的填充和消費。微觀上看匯編的是實現操作系統級別的原子操作的基石。 原文地址:https://www.xilidou.com/2018/02/01/java-cas/ CAS 是現代操作系統,解決并發問題的一個重要手段,最近在看 eureka 的源碼的時候。...
摘要:首先在下創建文件夾,在下創建文件,所有的路由控制都寫在這個文件里。表示路徑,表示顯示的頁面要顯示哪個文件,表示的嵌套的路由。 說明 上一篇地址--創建 蒼渡大神Github項目源碼地址--源碼地址 下一篇地址--引入UI框架 home.vue 創建 根據源碼,先在src文件夾下新建文件夾page,在page中新建home文件夾,在home文件夾中新建home.vue,hom...
摘要:現在終止一個線程,基本上只能靠曲線救國式的中斷來實現。中斷機制的核心在于中斷狀態和異常中斷狀態設置一個中斷狀態清除一個中斷狀態方法同時會返回線程原來的中斷的狀態。中斷異常中斷異常一般是線程被中斷后,在一些類型的方法如中拋出。 前言 系列文章目錄 線程中斷是一個很重要的概念,通常,取消一個任務的執行,最好的,同時也是最合理的方法,就是通過中斷。 本篇我們主要還是通過源碼分析來看看中斷的概...
摘要:方法,刪除當前線程綁定的這個副本數字,這個值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測法來處理沖突的,就是每次增加的步長,根據參考資料所說,選擇這個數字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關于ThreadLocal常見的疑問,希望可以通過這篇學...
閱讀 3665·2021-09-07 09:59
閱讀 724·2019-08-29 15:12
閱讀 808·2019-08-29 11:14
閱讀 1313·2019-08-26 13:27
閱讀 2666·2019-08-26 10:38
閱讀 3137·2019-08-23 18:07
閱讀 1277·2019-08-23 14:40
閱讀 1928·2019-08-23 12:38