Java線程狀態(tài)及切換
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質文章,第一時間送達
? 作者?|??血夜之末
來源 |? urlify.cn/UrAnym
一、什么是Java線程狀態(tài)
在Java程序中,用于描述Java線程的六種狀態(tài):
新建(NEW):當前線程,剛剛新建出來,尚未啟動。
運行(RUNNABLE):當前線程,處于競爭CPU時間分片或已經(jīng)獲得CPU時間片的狀態(tài)。
等待(WAITTING):當前線程,處于休眠,不參與CPU時間片競爭的狀態(tài)。
定時等待(TIMED_WAITTING):當前線程,處于定時休眠,暫時不參與CPU時間片競爭的狀態(tài)。
阻塞(BLOCKED):當前線程,處于阻塞,不參與CPU時間片競爭的狀態(tài)。
終止(TERMINATED):當前線程,處于最終停止的狀態(tài)。
新建狀態(tài),只能進入運行狀態(tài)。而終止狀態(tài)無法再轉為其他狀態(tài)。
等待/定時等待與阻塞,差別就是后者需要一個事件信號(如其他線程放棄當前線程需要的排他鎖),才可以進行狀態(tài)切換。當然,強行關閉也是可以的。
Java線程的實現(xiàn)并不受JVM規(guī)范約束,故不同虛擬機的實現(xiàn),往往不同。目前主流的HotSpot是將每個Java線程直接映射到一個操作系統(tǒng)的原生線程,從而由操作系統(tǒng)完成一系列的線程調度
二、哪里看Java線程狀態(tài)
查看Java線程狀態(tài),主要存在三種方式:
java.lang.Thread.State下可以直接看到Java的六種線程狀態(tài)
Java運行時,程序內部可以通過Thread.getState()獲取目標線程狀態(tài)
Java運行時,程序外部可以通過jstack等工具,查看線程狀態(tài)
有關jstack等工具等使用,后續(xù)會有博客,專門闡述。
三、什么時候變換Java線程狀態(tài)
Java線程狀態(tài)的切換嘛。不啰嗦,直接上圖。
這張圖涵蓋了Java線程狀態(tài)切換的各類方法。相較網(wǎng)上一些圖片,更為詳盡一些。
如果有所遺漏,可以告訴我,我會及時填補上。
四、誰在使用Java線程狀態(tài)
日常開發(fā)中,我們并不會直接與線程狀態(tài)進行交互。
我們往往直接使用JDK包裝好的工具,如JUC包下的各類工具等。
舉個栗子
線程池中的應用
位置:com.sun.corba.se.impl.orbutil.threadpool.ThreadPoolImpl#close
????//?Note?that?this?method?should?not?return?until?AFTER?all?threads?have?died.
????public?void?close()?throws?IOException?{
????????//?Copy?to?avoid?concurrent?modification?problems.
????????List?copy?=?null;
????????synchronized?(workersLock)?{
????????????copy?=?new?ArrayList<>(workers);
????????}
????????for?(WorkerThread?wt?:?copy)?{
????????????wt.close();
????????????while?(wt.getState()?!=?Thread.State.TERMINATED)?{
????????????????try?{
????????????????????wt.join();
????????????????}?catch?(InterruptedException?exc)?{
????????????????????wrapper.interruptedJoinCallWhileClosingThreadPool(exc,?wt,?this);
????????????????}
????????????}
????????}
????????threadGroup?=?null;
????}
實際查看JDK后發(fā)現(xiàn),JDK中其實也沒有辣么多的實例,并且大多數(shù)直接關聯(lián)線程狀態(tài)的,也是一些狀態(tài)查看的東東。
這在文章開頭就說,Java線程操作,是很底層的,甚至其實現(xiàn)都不包含在虛擬機規(guī)范中。
主流的HotSpot,也是直接將Java線程映射到系統(tǒng)線程,由系統(tǒng)進行一系列的線程調度處理。
所以,在JDK中,是直接對線程狀態(tài)進行處理的部分是很少的。
五、為什么需要線程狀態(tài)
1.為什么需要線程狀態(tài)這一概念
這個問題,可以從兩個角度來說明:生命周期與資源管理
一方面,線程狀態(tài)很好地刻畫了線程的整個生命周期,對生命周期中不同階段進行了有效劃分。
另一方面,資源是有限的,需求是無限的。所以需要將系統(tǒng)資源有意識地進行調度,合理利用比較優(yōu)勢,追求帕累托最優(yōu)。
實現(xiàn)后者的,就是利用線程在生命周期的不同階段這一天然屬性帶來的狀態(tài)刻畫,進行的分組。CPU調度只需要關注運行狀態(tài)的線程。而阻塞,等待等線程,都有著屬于自己的一套處理方式。最終獲得資源(開發(fā)時對復雜性的應對,運行時對系統(tǒng)資源對消耗,應用者心智模型的成長等)的優(yōu)化分配。
2.JDK中為什么需要定義Java線程狀態(tài)
前文有說到,Java中很少直接使用到線程狀態(tài)。那么為什么還要在JDK中定義Java的六種線程狀態(tài)呢?
一方面,通過信息透明的方式,降低使用者使用時建立心智模型的成本。如,現(xiàn)在的我們可以通過打印日志,打斷點,快速了解Java的各個線程狀態(tài),并清楚了解產生的原因。從而大大提高了我們對Java線程的認識,進而更為愉快地擁抱JUC包下諸如線程池等工具。
另一方面,通過可以直接應用的狀態(tài)枚舉,我們可以很好地對現(xiàn)有工具進行二次開發(fā)等。如我們可以通過擴展AQS,并在其中添加線程狀態(tài)的校驗,從而得到定制化的線程同步工具。
六、如何使用線程狀態(tài)
使用的方式,我已經(jīng)在上文說了:學習Java線程,定制線程相關工具開發(fā)。
這里給出一個有關線程學習的demo:
/**
?*?@program:?learning
?*?@description:?用于確認線程狀態(tài)問題
?*?@author:?Jarry
?*?@create:?2020-10-26?22:25
?**/
public?class?ThreadState?{
????public?static?void?main(String[]?args)?{
????????threadStateTest();
//????????threadStateTest2();
//????????threadStateWithBlocked();
//????????threadStateWithException();
//????????threadStateWithSuspend();
????}
????/**
?????*?實踐證明:Thread.suspend()與Thread.resume()不會改變線程狀態(tài)
?????*?線程狀態(tài)該是Waiting,就Waiting。該Timed_Waiting就Timed_Waiting
?????*?Thread.suspend()與Thread.resume()只是掛起目標線程(并且不會釋放鎖資源)
?????*/
????private?static?void?threadStateWithSuspend()?{
????????Thread?thread1?=?new?Thread(()?->?{
//????????????LockSupport.park();
????????????LockSupport.parkNanos(2000000000);
????????});
????????thread1.start();
????????printThreadState(thread1);
????????LockSupport.parkNanos(500000000);
????????printThreadState(thread1);
????????thread1.suspend();
????????printThreadState(thread1);
????????LockSupport.parkNanos(500000000);
????????printThreadState(thread1);
????????thread1.resume();
????????LockSupport.parkNanos(500000000);
????????printThreadState(thread1);
//????????LockSupport.unpark(thread1);
????}
????/**
?????*?展現(xiàn)線程阻塞狀態(tài)
?????*/
????private?static?void?threadStateWithBlocked()?{
????????Runnable?runnable?=?new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????synchronized?(ThreadState.class)?{
//????????????????????LockSupport.parkNanos(2000000000);
????????????????????LockSupport.park();
????????????????}
????????????}
????????};
????????Thread?thread1?=?new?Thread(runnable);
????????Thread?thread2?=?new?Thread(runnable);
????????thread1.start();
????????LockSupport.parkNanos(500000000);
????????thread2.start();
????????//?加上以下時間間隔,則結果:Runnable->Blocked
????????//?推論:Thread.start()會將線程狀態(tài)設置為Runnable,然后在遇到sync的鎖,再切換為Blocked狀態(tài)
//????????LockSupport.parkNanos(500000000);
????????printThreadState(thread2);
????????LockSupport.parkNanos(500000000);
????????printThreadState(thread2);
????????LockSupport.parkNanos(500000000);
????????LockSupport.unpark(thread1);
????????LockSupport.unpark(thread2);
????}
????/**
?????*?由于底層實現(xiàn)機制的不同(相較于其他waiting的方法),無法直接進行Object.wait(),否則會拋出以下異常
?????*?@exception?java.lang.IllegalMonitorStateException
?????*?object.wait()進行wait的方法,是直接對其wait_set進行操作
?????*/
????private?static?void?threadStateWithException()?{
????????Thread?thread1?=?new?Thread(()?->?{
????????????try?{
????????????????ThreadState.class.wait(2000);
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????});
????????thread1.start();
????????LockSupport.parkNanos(1000000000);
????????printThreadState(thread1);
????}
????/**
?????*?Object.wait()的使用
?????*/
????private?static?void?threadStateTest3()?{
????????Thread?thread1?=?new?Thread(()?->?{
????????????synchronized?(ThreadState.class)?{
????????????????try?{
????????????????????ThreadState.class.wait(2000);
????????????????}?catch?(InterruptedException?e)?{
????????????????????e.printStackTrace();
????????????????}
????????????}
????????});
????????thread1.start();
????????LockSupport.parkNanos(1000000000);
????????printThreadState(thread1);
????}
????/**
?????*?確定LockSupport.parkNacos()是否可以生成Time_Waiting狀態(tài)
?????*/
????private?static?void?threadStateTest2()?{
????????Thread?thread1?=?new?Thread(()?->?{
????????????LockSupport.parkNanos(2000000000);
????????});
????????thread1.start();
????????printThreadState(thread1);
????????LockSupport.parkNanos(1000000000);
????????printThreadState(thread1);
????}
????/**
?????*?查看到除Blocked以外的所有線程狀態(tài)
?????*/
????private?static?void?threadStateTest()?{
????????Thread?thread1?=?new?Thread(()?->?{
????????????synchronized?(ThreadState.class)?{
????????????????//?Runnable
????????????????printThreadState(Thread.currentThread());
????????????????//?1.Thread.sleep(time)
????????????try?{
????????????????Thread.sleep(2000);
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????????????//?2.Object.wait(time)
//????????????????try?{
//????????????????????ThreadState.class.wait(2000);
//????????????????}?catch?(InterruptedException?e)?{
//????????????????????e.printStackTrace();
//????????????????}
????????????????//?3.Thread.join(time)
//????????????????try?{
//????????????????????Thread.currentThread().join(2000);
//????????????????}?catch?(InterruptedException?e)?{
//????????????????????e.printStackTrace();
//????????????????}
????????????????//?4.LockSupport.parkNanos(time);
//????????????LockSupport.parkNanos(2000000000);
????????????????//?5.LockSupport.parkUntil(timeStamp);
//????????????LockSupport.parkUntil(System.currentTimeMillis()+2000);
????????????????LockSupport.park();
????????????}
????????});
????????thread1.setName("test_thread");
????????//?New
????????printThreadState(thread1);
????????thread1.start();
????????LockSupport.parkNanos(1000000000);
????????//?Timed_waiting
????????printThreadState(thread1);
????????LockSupport.parkNanos(2000000000);
????????//?Waiting
????????printThreadState(thread1);
????????LockSupport.unpark(thread1);
????????LockSupport.parkNanos(1000000000);
????????//?Terminated
????????printThreadState(thread1);
????}
????private?static?void?printThreadState(Thread?thread)?{
????????System.out.println("current?Thread("?+?thread.getName()+":"?+?thread.getId()?+?")?state:"?+?thread.getState());
????}
}代碼中有一些細碎的知識點,就不在這里贅述了。感興趣的小伙伴,可以自己看看注釋,自行驗證。
七、擴展:系統(tǒng)狀態(tài)(三態(tài)&五態(tài))
操作系統(tǒng)就包含進程管理,作業(yè)管理,文件管理等。其中進程管理,就涉及系統(tǒng)狀態(tài)的三態(tài)模型與五態(tài)模型。
其中,三態(tài)模型包含以下三種狀態(tài):
就緒狀態(tài)
運行狀態(tài)
阻塞狀態(tài)
而五態(tài)模型則包含以下五種狀態(tài):
運行狀態(tài)
靜止就緒態(tài)
活躍就緒態(tài)
靜止阻塞態(tài)
活躍阻塞態(tài)
具體狀態(tài)切換等,可以看我之前寫的一篇博客:
系統(tǒng)架構設計師-操作系統(tǒng)
最后,愿與諸君共進步。
八、附錄
補充:WAITTING/TIMED_WAITTING與BLOCKED的區(qū)別
其實,我之前也挺糾結這個問題的。
當時的想法是WAITTING/TIMED_WAITTING是由JVM自己維持,而BLOCKED是由系統(tǒng)維持的。后面看到主流的HotSpot是將線程映射到系統(tǒng)原生線程的,所以這個想法大概率是錯誤的。
那么兩者區(qū)別是什么呢?
直到我在上文的示例代碼中,BLOCKED狀態(tài)的線程,在其他線程釋放其所需的鎖時,該線程是先轉為RUNNING狀態(tài),再變?yōu)槠渌麪顟B(tài)。這引起我的注意。
最后在stackOverFlow的一篇文章(Difference between WAIT and BLOCKED thread states)中,看到這樣的解釋:
The important difference between the blocked and wait states is the impact on the scheduler. A thread in a blocked state is contending for a lock; that thread still counts as something the scheduler needs to service, possibly getting factored into the scheduler's decisions about how much time to give running threads (so that it can give the threads blocking on the lock a chance).
Once a thread is in the wait state the stress it puts on the system is minimized, and the scheduler doesn't have to worry about it. It goes dormant until it receives a notification. Except for the fact that it keeps an OS thread occupied it is entirely out of play.
This is why using notifyAll is less than ideal, it causes a bunch of threads that were previously happily dormant putting no load on the system to get woken up, where most of them will block until they can acquire the lock, find the condition they are waiting for is not true, and go back to waiting. It would be preferable to notify only those threads that have a chance of making progress.
(Using ReentrantLock instead of intrinsic locks allows you to have multiple conditions for one lock, so that you can make sure the notified thread is one that's waiting on a particular condition, avoiding the lost-notification bug in the case of a thread getting notified for something it can't act on.)
簡單說,就是CPU時間片不會考慮WAITTING/TIMED_WAITTING狀態(tài)。
但是,雖然BLOCKED狀態(tài)的線程無法獲得CPU時間片,但是系統(tǒng)調度時,依舊會考慮BLOCKED狀態(tài)的線程,將其置于調度計算中。
如果哪位小伙伴對這方面有了解,希望可以聊一聊。
參考
JDK
Java虛擬機規(guī)范
《系統(tǒng)架構設計師教程》
《深入理解Java虛擬機》
系統(tǒng)架構設計師-操作系統(tǒng)
java中的interrupt()方法
Lifecycle and States of a Thread in Java
Difference between WAIT and BLOCKED thread states
粉絲福利:實戰(zhàn)springboot+CAS單點登錄系統(tǒng)視頻教程免費領取
???
?長按上方微信二維碼?2 秒 即可獲取資料
感謝點贊支持下哈?
