系列文章目錄 java
上一篇咱們討論了線程的建立,本篇咱們來聊一聊線程的狀態轉換以及經常使用的幾個比較重要的方法。面試
本篇依然是經過源碼分析來了解這些知識。segmentfault
本文源碼基於jdk1.8 。app
閱讀完本文,你應當有能力回答如下常見面試題:函數
在Thread類中, 線程狀態是經過threadStatus
屬性以及State
枚舉類實現的:oop
/* Java thread status for tools, * initialized to indicate thread 'not yet started' */ private volatile int threadStatus = 0; public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */ RUNNABLE, /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate. */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; } /** * Returns the state of this thread. * This method is designed for use in monitoring of the system state, * not for synchronization control. * * @return this thread's state. * @since 1.5 */ public State getState() { // get current thread state return sun.misc.VM.toThreadState(threadStatus); }
從源碼中能夠看出, 線程一共有6種狀態, 其狀態轉換關係以下圖所示:
源碼分析
值得一提的是,從狀態的定義中能夠看出,RUNNABLE狀態包含了咱們一般所說的running
和ready
兩種狀態。this
源碼中currentThread定義以下:spa
/** * Returns a reference to the currently executing thread object. * * @return the currently executing thread. */ public static native Thread currentThread();
可見,它是一個靜態方法,而且是一個native方法,返回的是當前正在執行的線程。線程
愛思考的同窗可能就要問了,如今咱都多核CPU了,同一時刻能夠有多個線程跑在不一樣的CPU核心上,那當前正在執行的線程有多個,到底返回的是哪個呢?
其實,這裏"當前正在執行的線程"指的是當前正在執行這段代碼的線程。
咱們知道,線程是CPU調度的最小單位,任意一段代碼總得由一個線程執行,因此該方法返回的是正在執行Thread.currentThread
這行代碼的線程,例如:
public class ThreadTest { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); } }
輸出:
main
咱們知道當一個Java程序啓動之後,有一個線程就會立馬跑起來,這就是一般所說的Main線程,main線程將會執行java的入口方法main方法,因此當前正在執行Thread.currentThread()方法的線程就是main線程。
談起sleep方法, 被問的最多的兩個問題就是:
這些問題的答案, 你在源碼裏都能找獲得。咱們直接來看源碼:
/** * 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;
可見, sleep方法也是一個靜態方法, 而且是native方法, 從註釋Causes the currently executing thread to sleep
中能夠看出, 它做用於當前正在執行的線程
, 因此上面那個問題咱們就能回答了:
Thread.sleep() 與 Thread.currentThread().sleep() 沒有區別
若是硬要說他們有什麼區別的話, 那就是一個是用類直接調用靜態方法, 一個是用類的實例調用靜態方法.
另外, 上面的註釋中還有一句很是重要的話:
The thread does not lose ownership of any monitors.
也就是說, 雖然sleep函數使當前線程讓出了CPU, 可是, 當前線程仍然持有它所得到的監視器鎖, 這與同時讓出CPU資源和監視器鎖資源的wait方法是不同的。
sleep方法還有另一個版本:
/** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds plus the specified * number of nanoseconds, 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 * * @param nanos * {@code 0-999999} additional nanoseconds to sleep * * @throws IllegalArgumentException * if the value of {@code millis} is negative, or the value of * {@code nanos} is not in the range {@code 0-999999} * * @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 void sleep(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException("nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } sleep(millis); }
這個方法多加了納秒級別的延時參數, 可是咱們看源碼就知道, 這個多加的納秒級別的延時並無什麼用, 最終該函數仍是調用了上面的單參數native sleep方法, 延時仍是毫秒級別的, 多出來的參數最可能是讓當前毫秒級別的延時增長1毫秒.
還記得咱們上次講的wait方法嗎?咱們來對比下:
public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException("nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); }
怎麼樣?是否是很像?二者只不過在從納秒向毫秒的進位處有細微的差異,我猜這個不統一是歷史緣由致使的。
另外,值得一提的是,wait有無參的wait()
方法,它調用的是wait(0)
,表示無限期等待,而sleep並無無參數的版本,那麼sleep(0)
表明什麼呢?
這一點在源碼裏面並無說起,可是經過猜想sleep方法的定義咱們知道,它是讓出CPU 0毫秒,這聽上去好像沒有什麼意義,但其實調用Thread.sleep(0)的當前線程確實被「凍結」了一下,讓其餘線程有機會優先執行。也就是說當前線程會釋放一些未用完的時間片給其餘線程或進程使用,就至關於一個讓位動做,這看上去就和下面要說的yield方法很像了。
既然上面談到了sleep(0)方法, 就不得不提yield方法了:
/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield();
yield方法也是一個native方法, 從它的註釋能夠看出A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.
它對於CPU只是一個建議, 告訴CPU, 當前線程願意讓出CPU給其餘線程使用, 至於CPU採不採納, 取決於不一樣廠商的行爲, 有可能一個線程剛yield出CPU, 而後又立馬得到了CPU。與之相對, sleep方法必定會讓出CPU資源, 而且休眠指定的時間, 不參與CPU的競爭.
因此調用yield方法不會使線程退出RUNNANLE
狀態,頂多會使線程從running 變成 ready,
可是sleep方法是有可能將線程狀態轉換成TIMED_WAITING
的。
isAlive
方法用於檢查線程是否還活着,它是一個native方法,但不是靜態方法,也就是說它必須被線程的實例所調用。
其實你們能夠思考一下它爲何不是靜態方法,由於靜態方法通常都是做用於當前正在執行的線程
,既然是「當前正在執行」,那必然是Alive
的,因此做爲靜態方法調用並無意義。
/** * 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();
join方法是另外一個能將線程狀態轉換成WAITING
或者TIMED_WAITING
的,它和wait方法同樣,有三個版本,咱們一個個來看:
/** * Waits at most {@code millis} milliseconds for this thread to * die. A timeout of {@code 0} means to wait forever. * * <p> This implementation uses a loop of {@code this.wait} calls * conditioned on {@code this.isAlive}. As a thread terminates the * {@code this.notifyAll} method is invoked. It is recommended that * applications not use {@code wait}, {@code notify}, or * {@code notifyAll} on {@code Thread} instances. * * @param millis * the time to wait 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 final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
這段源碼註釋的開頭部分就告訴了咱們join方法的做用:
Waits at most {@code millis} milliseconds for this thread to die. A timeout of {@code 0} means to wait forever.
也就是說,該方法等待this thread
終止,最多等指定的時間,若是指定時間爲0,則一直等。
這裏有兩個問題須要弄清楚:
this thread
終止?this thread
指的是哪一個線程?爲了便於說明,咱們直接來看一個例子:
public class JoinMethodTest { private static void printWithThread(String content) { System.out.println("[" + Thread.currentThread().getName() + "線程]: " + content); } public static void main(String[] args) { printWithThread("開始執行main方法"); Thread myThread = new Thread(() -> { printWithThread("我在自定義的線程的run方法裏"); printWithThread("我立刻要休息1秒鐘, 並讓出CPU給別的線程使用."); try { Thread.sleep(1000); printWithThread("已經休息了1秒, 又從新得到了CPU"); printWithThread("我休息好了, 立刻就退出了"); } catch (InterruptedException e) { e.printStackTrace(); } }); try { myThread.start(); printWithThread("我在main方法裏面, 我要等下面這個線程執行完了才能繼續往下執行."); myThread.join(); printWithThread("我在main方法裏面, 立刻就要退出了."); } catch (InterruptedException e) { e.printStackTrace(); } } }
在上面的例子中,咱們在main方法中調用了 myThread.join()
,注意上面這段代碼有兩個線程,一個是執行main方法的線程,一個是咱們自定義的myThread
線程,因此上面的兩個問題的答案是:
this thread
的終止,由於咱們在main方法中調用了myThread.join()
this thread
線程指的是myThread
線程,由於咱們在myThread對象上調用了join方法。上面這段代碼的執行結果爲:
[main線程]: 開始執行main方法 [main線程]: 我在main方法裏面, 我要等下面這個線程執行完了才能繼續往下執行. [Thread-0線程]: 我在自定義的線程的run方法裏 [Thread-0線程]: 我立刻要休息1秒鐘, 並讓出CPU給別的線程使用. [Thread-0線程]: 已經休息了1秒, 又從新得到了CPU [Thread-0線程]: 我休息好了, 立刻就退出了 [main線程]: 我在main方法裏面, 立刻就要退出了.
從運行結果能夠看出,雖然myThread線程(即Thread-0線程)中途讓出了CPU, main線程仍是必須等到其執行完畢了才能繼續往下執行,咱們如今修改一下代碼,讓main線程最多等0.5秒,即將myThread.join()
改成myThread.join(500);
,則結果以下:
[main線程]: 開始執行main方法 [main線程]: 我在main方法裏面, 我要等下面這個線程執行完了才能繼續往下執行. [Thread-0線程]: 我在自定義的線程的run方法裏 [Thread-0線程]: 我立刻要休息1秒鐘, 並讓出CPU給別的線程使用. [main線程]: 我在main方法裏面, 立刻就要退出了. [Thread-0線程]: 已經休息了1秒, 又從新得到了CPU [Thread-0線程]: 我休息好了, 立刻就退出了
咱們看到,因爲main線程最多等待myThread 0.5秒,在myThread休眠的一秒內,它就不等了,繼續往下執行,而隨後myThread搶佔到CPU資源繼續運行。
經過列子有了感性的認識後,咱們再來看源碼,首先看join(0)部分:
public final synchronized void join(long millis) throws InterruptedException { ... if (millis == 0) { while (isAlive()) { wait(0); } } else { ... } ... }
這是一個自旋操做,注意,這裏的isAlive
和wait(0)
方法都是線程實例的方法,在上面的例子中就是myThread
的方法,Thread雖然是一個線程類,但只是特殊在它的native方法上,除此以外,它就是個普通的java類,而java中全部的類都繼承自Object
類,因此Thread類繼承了Object的wait方法,myThread
做爲線程類的實例,天然也有wait方法。
咱們以前說wait方法的時候提到過,執行wait方法必須拿到監視器鎖,而且必須在同步代碼塊中調用,這裏咱們檢查join方法發現,它確實被synchronized
關鍵字修飾,而且是一個非靜態方法,因此它使用的是當前對象實例的監視器鎖(this)。
好像開始複雜了,咱們從頭至尾捋一捋(注意了!敲黑板了!這段比較繞! ):
myThread.join()
,main方法由main線程執行,因此執行myThread.join()
這行代碼的「當前線程」是main線程。myThread
線程是否還存活,注意,這裏的isAlive是myThread線程的方法,它是檢查myThread線程是否還活着,而不是當前線程(當前線程是執行isAlive方法的線程,即main線程)。WAITING
狀態。WAITING
狀態被喚醒後(經過notify,notifyAll或者是假喚醒), 將繼續競爭監視器鎖,當成功得到監視器鎖後,他將從調用wait的地方恢復,繼續運行。因爲wait方法在while循環中,則它將繼續檢查myThread
線程是否存活,若是仍是沒有終止,則繼續掛起等待。myThread
線程終止運行(或者有中斷異常拋出)。有的細心的同窗可能就要問了: 要是沒有人調用notify
或者notifyAll
,也沒有假喚醒狀態的發生,那main線程不就一直被wait(0)
方法掛起了嗎?這樣以來不就連檢測myThread
線程是否存活的機會都沒有嗎?這樣即便myThread
終止了,也沒法退出啊。
關於這一點,註釋中實際上是作了解釋的:
As a thread terminates the {@code this.notifyAll} method is invoked.
咱們知道,wait(0)方法的監視器鎖就是myThread對象(this), 而當myThread終止執行時,this.notifyAll會被調用,因此全部等待this鎖的線程都會被喚醒,而main線程就是等待在這個監視器鎖上的線程,所以myThread運行結束時,main線程會從wait方法處被喚醒。
另外,註釋中還多加了一句:
It is recommended that applications not use {@code wait}, {@code notify}, or {@code notifyAll} on {@code Thread} instances.
這個推薦仍是頗有必要的,至於爲何,就給你們留做思考題吧<( ̄︶ ̄)>
不過我這裏再囉嗦一句,必定要分清執行代碼的線程和方法所屬的線程類所表明的線程!
例如,在上面的例子中:
myThread.join()
是myThread對象的方法,可是執行這個方法的是main線程;isAlive()
是myThread對象的方法,可是執行這個方法的是main線程,而這個方法檢測是myThread線程是否活着wait(0)
是myThread對象的方法,可是執行這個方法的是main線程,它使得main線程掛起,可是main線程是在myThread對象表明的monitor上掛起。這裏最重要的是區分「myThread對象」和「myThread線程」,myThread對象有時候表明了myThread線程,例如myThread對象的isAlive
方法,檢測的就是它表明的myThread線程是否活着,可是其實大多數時候,myThread對象就是普通的java對象,這個對象的方法一般也都是由其餘線程(例如上面例子中的main線程)來執行的,對於咱們自定義的線程來講(例如上面的myThread線程),一般由它本身執行的方法就只有傳進入的run
方法了。
再回到上面的例子,從上面的分析中能夠看出,join(0)方法實現了必定程度上的線程同步,即當前線程只有等join方法所屬的線程對象所表明的線程終止執行了才能繼續往下執行,不然將一直掛起等待。
這一點也說明使用join(0)是很危險的,由於若是myThread
線程由於得不到資源一直被掛起,而main線程又在等待myThread
線程終止,則程序永遠會停在那裏,沒法終止,因此源碼中提供了限時等待的版本:
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; ... if (millis == 0) { ... } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
與無限期等待不一樣的是,限時等待只等待指定時間,若是指定的時間到了就直接從循環中跳出來,使用的wai方法也是限時wait的版本,定時時間到了以後,main線程會被自動喚醒。上面的代碼是自解釋的,我就再也不贅述了。
接下來咱們再來看看其餘兩個版本的join方法:
public final void join() throws InterruptedException { join(0); } public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException("nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); }
可見,其餘兩個版本最終調用的都是咱們分析的初版本,這和wait方法,sleep方法很像,至於爲何wait方法和join方法都提供了無參方法而sleep方法沒有,我我的認爲是爲了保持語義的一致性:
wait()
和join()
分別和wait(0)
和join(0)
等價,他們都表明了無限期等待,而sleep(0)並不表明無限期等待,因此sleep方法沒有無參的形式,以防止語義上的混亂。除這點以外,這三個方法在兩個參數的版本XXX(long millis, int nanos)中的實現,都大同小異。
另外最後一點值得注意的是,咱們在join
方法中只調用了isAlive
方法檢測線程是否存活,並無啓動這個線程,也就是說,若是咱們想要實現當前線程等待myThread
線程執行完成以後再執行的效果,就必須在調用myThread.join()
以前調用myThread.start()
讓線程先跑起來,不然join
方法發現isAlive
爲false會當即退出,myThread
線程就不會被執行,你們能夠將myThread.start()
註釋掉本身跑一跑試試看。
(完)
查看更多系列文章:系列文章目錄