Java線程生命周期的終止與復(fù)位
Thread生命周期
生命周期概述
Java的線程狀態(tài)描述放在Thread
類里面的枚舉類State
中.總共包含了6中狀態(tài)(從出生到死亡)。
public enum State { /** * 尚未啟動(dòng)的線程的線程狀態(tài) (沒有start) */ NEW, /** * 可運(yùn)行線程的線程狀態(tài),是可以運(yùn)行的線程狀態(tài)(并不是在運(yùn)行) * 這個(gè)狀態(tài)在Java虛擬機(jī)中進(jìn)行,但它可能等待來自操作系統(tǒng)的其他資源,比如CPU。 * 內(nèi)部包含了兩個(gè)狀態(tài) 【RUNNING】,【READY】這兩個(gè)狀態(tài)是可以互相流轉(zhuǎn)的 * 調(diào)用了start后線程就處于 READY 狀態(tài) ,等待操作系統(tǒng)分配CPU時(shí)間片,分配后進(jìn)入 RUNNING 狀態(tài)。 * 當(dāng)調(diào)用 yield() 方法后,只是謙讓的允許當(dāng)前線程讓出 CPU ,但是不一定讓,由操作系統(tǒng)決定,如果讓 * 了當(dāng)前線程就會(huì)進(jìn)入 READY 狀態(tài),等待系統(tǒng)分配CPU時(shí)間片再次進(jìn)入 RUNNING 狀態(tài)。 */ RUNNABLE, /** * 阻塞狀態(tài)。 * 線程阻塞,等待監(jiān)視器鎖的狀態(tài),獲取監(jiān)視器鎖后會(huì)進(jìn)入 RUNNABLE 狀態(tài) * 當(dāng)發(fā)生線程鎖競爭狀態(tài)下,沒有獲取到鎖的線程會(huì)被掛起進(jìn)入阻塞狀態(tài),比如synchronized鎖。 */ BLOCKED, /** * 等待線程的線程狀態(tài) * 線程調(diào)用以下方法會(huì)處于等待狀態(tài):Object.wait()不超時(shí)、Thread.join()不超時(shí)等方法 * 一個(gè)處于等待狀態(tài)的線程正在等待另一個(gè)線程執(zhí)行特定動(dòng)作,例如: * 一個(gè)線程調(diào)用了Object.wait()方法在一個(gè)對象上正在等待另一個(gè)線程調(diào)用Object.nofify()或者 * Object.nofifyAll()方法開啟那個(gè)對象 * 一個(gè)調(diào)用了Thread.join()方法的線程正在等待指定線程終止 */ WAITING, /** * 具有指定等待時(shí)間的等待線程的線程狀態(tài),調(diào)用一下方法會(huì)處于這個(gè)狀態(tài): Object.wait() 超時(shí)、 * Thread.join()超時(shí) Thread.sleep(long) 等方法 */ TIMED_WAITING, /** * 已終止線程的線程狀態(tài) * 線程執(zhí)行完畢或者發(fā)生異常終止執(zhí)行 */ TERMINATED; }
線程生命周期流程圖
線程生命周期測試
public class ThreadStatusDemo { public static void main(String[] args) throws InterruptedException { // 測試 NEW RUNNABLE TERMINATED Thread terminated_thread = new Thread(() -> { long start = System.currentTimeMillis(); // 運(yùn)行三秒 ,打印TERMINATED_THREAD線程runnable狀態(tài) while (System.currentTimeMillis()-start<3000){} }, "TERMINATED_THREAD"); // NEW Thread.State state = terminated_thread.getState(); System.out.println(terminated_thread.getName()+" :state = " + state); terminated_thread.start(); TimeUnit.SECONDS.sleep(1); // RUNNABLE Thread.State state1 = terminated_thread.getState(); System.out.println(terminated_thread.getName()+"state1 = " + state1); TimeUnit.SECONDS.sleep(5); Thread.State state2 = terminated_thread.getState(); // TERMINATED System.out.println(terminated_thread.getName()+"state2 = " + state2); // RUNNABLE new Thread(() -> { while (true) { } }, "Runnle_Thread").start(); // TIMED_WAITING new Thread(() -> { while (true) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Time_Waiting_Thread").start(); // WAITING new Thread(() -> { while (true) { synchronized (ThreadStatusDemo.class) { try { ThreadStatusDemo.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "Waiting_Thread").start(); // 這兩個(gè)看誰先搶占到cpu獲得鎖,另一個(gè)就blocked // timed_waiting new Thread(new BlockedDemo(), "Blocke01_Thread").start(); // blocked new Thread(new BlockedDemo(), "Blocke02_Thread").start(); } static class BlockedDemo extends Thread { @Override public void run() { synchronized (BlockedDemo.class) { while (true) { try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
啟動(dòng)線程
java中的啟動(dòng)
Java
啟動(dòng)一個(gè)線程調(diào)用start
方法,start方法內(nèi)部調(diào)用了 start0()
native方法。
public synchronized void start() { . . . boolean started = false; try { // 調(diào)用native方法 start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }
這個(gè)測試是為了驗(yàn)證上圖的正確性,只貼了部分.
Hotspot中的啟動(dòng)
查看指引:
在jvm.cpp
找到JVM_StartThread
方法。發(fā)現(xiàn)是先創(chuàng)建個(gè) JavaThread
作為本地線程然后啟動(dòng)這個(gè)本地線程(借助os【thread.cpp】,因?yàn)閖vm是跨平臺的,這里是以linux-os為示例)
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) JVMWrapper("JVM_StartThread"); JavaThread *native_thread = NULL; bool throw_illegal_thread_state = false; { MutexLocker mu(Threads_lock); if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) { throw_illegal_thread_state = true; } else { jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread)); size_t sz = size > 0 ? (size_t) size : 0; // 先創(chuàng)建一個(gè)JavaThread native_thread = new JavaThread(&thread_entry, sz); if (native_thread->osthread() != NULL) { native_thread->prepare(jthread); } } } if (throw_illegal_thread_state) { THROW(vmSymbols::java_lang_IllegalThreadStateException()); } assert(native_thread != NULL, "Starting null thread?"); if (native_thread->osthread() == NULL) { delete native_thread; if (JvmtiExport::should_post_resource_exhausted()) { JvmtiExport::post_resource_exhausted( JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS, "unable to create new native thread"); } THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(), "unable to create new native thread"); } // 然后啟動(dòng)這個(gè)本地線程 thread.cpp Thread::start(native_thread); JVM_END
JavaThread 創(chuàng)建線程:
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) : Thread() #if INCLUDE_ALL_GCS , _satb_mark_queue(&_satb_mark_queue_set), _dirty_card_queue(&_dirty_card_queue_set) #endif // INCLUDE_ALL_GCS { if (TraceThreadEvents) { tty->print_cr("creating thread %p", this); } initialize(); _jni_attach_state = _not_attaching_via_jni; set_entry_point(entry_point); os::ThreadType thr_type = os::java_thread; thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread : os::java_thread // 調(diào)用os(操作系統(tǒng))創(chuàng)建個(gè)線程 os::create_thread(this, thr_type, stack_sz); _safepoint_visible = false; . . . }
thread.cpp 啟動(dòng)線程:
// tips: 啟動(dòng)線程的方法 void Thread::start(Thread* thread) { trace("start", thread); // Start is different from resume in that its safety is guaranteed by context or // being called from a Java method synchronized on the Thread object. if (!DisableStartThread) { if (thread->is_Java_thread()) { // Initialize the thread state to RUNNABLE before starting this thread. // Can not set it after the thread started because we do not know the // exact thread state at that time. It could be in MONITOR_WAIT or // in SLEEPING or some other state. // tips:啟動(dòng)之后設(shè)置線程的狀態(tài)為 可運(yùn)行狀態(tài) RUNNABLE java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(), java_lang_Thread::RUNNABLE); } // 借助操作系統(tǒng)啟動(dòng)線程 os::start_thread(thread); } }
線程中斷與復(fù)位
不要使用stop方法
線程的終止不要簡單的調(diào)用 stop
方法,這個(gè)方法和其他的線程控制方法(suspend
,resume
)一樣都是過期了不建議使用的,這些方法都是不安全的。 例如stop()
方法在結(jié)束一個(gè)線程的時(shí)候并不保證線程資源的正常釋放,因此可能導(dǎo)致出現(xiàn)一些不確定的狀態(tài)。 按照人類邏輯來理解:T1線程調(diào)用方法修改T2線程的狀態(tài),但是T2現(xiàn)在在做什么T1是不清楚的,所以強(qiáng)制他關(guān)閉就是不安全的,就好比在Linux
中使用 kill -9
殺掉一個(gè)進(jìn)程。
使用interrupt方法
interrupt()
方法只是修改了被中斷線程的中斷標(biāo)志 ,并沒有做什么過分的事兒。就像平時(shí)寫代碼的時(shí)候修改某對象的標(biāo)志,對象自己通過標(biāo)志類決定執(zhí)行什么邏輯。這里也是一樣,interrupt()
方法修改中斷標(biāo)志,被中斷的線程,自己決定做什么事兒(中斷或者不中斷都是被中斷線程自己決定的,外部只是通知他,不是強(qiáng)迫他)。追一下源碼。
1.Java調(diào)用interrupt
方法
2.通過指引找到 jvm.cpp#JVM_Interrupt
方法
thread.cpp interrupt 借用操作系統(tǒng)。直接通過系統(tǒng)調(diào)用 interrupt void Thread::interrupt(Thread* thread) { trace("interrupt", thread); debug_only(check_for_dangling_thread_pointer(thread);) // tips: 調(diào)用操作系統(tǒng)的interrupt方法 os::interrupt(thread); }
這里還是以os_linux.cpp為例最終調(diào)用osthread的set_interrupted修改狀態(tài)
這里就印證了上方的 Thread.interrupt()
只是修改了線程的一個(gè)標(biāo)志位 ,并沒有做什么過分的事兒。
線程的復(fù)位
interrupted
與isInterrupted
這兩個(gè)放在一起是因?yàn)樗麄兊讓佣际钦{(diào)用的同一個(gè)native方法isInterrupted()
只是給了不同的入?yún)ⅰ?再就是,有過面試官問到他兩的區(qū)別,所以干脆放在一起。首先說結(jié)論 ,isInterrupted()
會(huì)返回線程的中斷狀態(tài),interrupted()
不僅會(huì)返回中斷狀態(tài),而且如果線程處于狀態(tài)狀態(tài)還會(huì)將線程終端狀態(tài)復(fù)位(清除中斷狀態(tài))。
os_linux.cpp的is_interrupted()
方法印證了上面說的isInterrupted()
會(huì)返回線程的中斷狀態(tài),interrupted()
不僅會(huì)返回中斷狀態(tài),而且如果線程處于狀態(tài)狀態(tài)還會(huì)將線程終端狀態(tài)復(fù)位(清除中斷狀態(tài))。
其他的線程復(fù)位
在Java
中只要拋出了InnterruptException
異常的方法都對線程進(jìn)行了復(fù)位。先理順下為什么要這么做:查看下基本上拋出InnterruptException
異常的方法都是線程阻塞方法,比如sleep()
,wait()
,join()
。這類方法執(zhí)行后線程會(huì)處于TIMED_WAITING
或者WAITING
狀態(tài),處于這類狀態(tài)的線程是不受控的(線程喪失了對自己的主導(dǎo),需要其他的線程喚醒,或者阻塞時(shí)間到達(dá)才能擁有自己的主導(dǎo)權(quán)),這個(gè)時(shí)候線程中斷,線程自己卻沒辦法處理。甚至可能永遠(yuǎn)等不到釋放而無法執(zhí)行中斷。所以,在線程是中斷狀態(tài)下,執(zhí)行方法讓線程阻塞,就要拋出一個(gè)異常告訴外界 ,我現(xiàn)在是阻塞狀態(tài),并且將中斷標(biāo)記復(fù)位,方便外界進(jìn)行處理(例如中斷線程的執(zhí)行或者繼續(xù)阻塞方法),相當(dāng)于給了外界一個(gè)改變線程狀態(tài)的入口。 以sleep()
為例追蹤下源碼:
通過指引找到 jcm.cpp#JVM_Sleep
方法入口就直接判斷線程的中斷狀態(tài)了 ,is_interrupted()
上面介紹過了,參數(shù)為true
就是清除中斷標(biāo)志并且返回清除之前的中斷狀態(tài)。這里線程是中斷狀態(tài)的就直接拋出 InnterruptException sleep interrupted
異常了。
到此這篇關(guān)于Java線程生命周期的終止與復(fù)位的文章就介紹到這了,更多相關(guān)Java線程生命周期內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java HashMap 如何正確遍歷并刪除元素的方法小結(jié)
這篇文章主要介紹了Java HashMap 如何正確遍歷并刪除元素的方法小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05MyBatis傳入數(shù)組集合類并使用foreach遍歷
這篇文章主要介紹了MyBatis傳入數(shù)組集合類并使用foreach遍歷,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02Spring Cloud Gateway不同頻率限流的解決方案(每分鐘,每小時(shí),每天)
SpringCloud Gateway 是 Spring Cloud 的一個(gè)全新項(xiàng)目,它旨在為微服務(wù)架構(gòu)提供一種簡單有效的統(tǒng)一的 API 路由管理方式。這篇文章主要介紹了Spring Cloud Gateway不同頻率限流(每分鐘,每小時(shí),每天),需要的朋友可以參考下2020-10-10關(guān)于 Java 的數(shù)據(jù)結(jié)構(gòu)鏈表
這篇文章主要介紹了關(guān)于 Java 的數(shù)據(jù)結(jié)構(gòu)鏈表的相關(guān)資料,需要的朋友可以參考下面文章內(nèi)容2021-09-09Spring自動(dòng)掃描無法掃描jar包中bean的解決方法
在日常開發(fā)中往往會(huì)對公共的模塊打包發(fā)布,然后調(diào)用公共包的內(nèi)容。然而,最近對公司的公共模塊進(jìn)行整理發(fā)布后。spring卻無法掃描到相應(yīng)的bean,下面這篇文章主要給大家介紹了關(guān)于Spring自動(dòng)掃描時(shí)無法掃描jar包中bean的解決方法,需要的朋友可以參考下。2017-06-06Java中高效的判斷數(shù)組中某個(gè)元素是否存在詳解
相信大家在操作Java的時(shí)候,經(jīng)常會(huì)要檢查一個(gè)數(shù)組(無序)是否包含一個(gè)特定的值?這是一個(gè)在Java中經(jīng)常用到的并且非常有用的操作。同時(shí),這個(gè)問題在Stack Overflow中也是一個(gè)非常熱門的問題。本文將分析幾種常見用法及其時(shí)間成本,有需要的朋友們可以參考借鑒。2016-11-11