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ā)生線程鎖競(jìng)爭(zhēng)狀態(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è)對(duì)象上正在等待另一個(gè)線程調(diào)用Object.nofify()或者
* Object.nofifyAll()方法開啟那個(gè)對(duì)象
* 一個(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;
}線程生命周期流程圖

線程生命周期測(cè)試
public class ThreadStatusDemo {
public static void main(String[] args) throws InterruptedException {
// 測(cè)試 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è)測(cè)試是為了驗(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是跨平臺(tái)的,這里是以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_ENDJavaThread 創(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方法
線程的終止不要簡(jiǎn)單的調(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í)候修改某對(duì)象的標(biāo)志,對(duì)象自己通過標(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異常的方法都對(duì)線程進(jìn)行了復(fù)位。先理順下為什么要這么做:查看下基本上拋出InnterruptException異常的方法都是線程阻塞方法,比如sleep(),wait(),join()。這類方法執(zhí)行后線程會(huì)處于TIMED_WAITING或者WAITING狀態(tài),處于這類狀態(tài)的線程是不受控的(線程喪失了對(duì)自己的主導(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA編程實(shí)現(xiàn)隨機(jī)生成指定長(zhǎng)度的密碼功能【大小寫和數(shù)字組合】
這篇文章主要介紹了JAVA編程實(shí)現(xiàn)隨機(jī)生成指定長(zhǎng)度的密碼功能,可生成帶有大小寫和數(shù)字組合的隨機(jī)字符串,需要的朋友可以參考下2017-07-07
基于Java實(shí)現(xiàn)Json文件轉(zhuǎn)換為Excel文件
這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)Json文件轉(zhuǎn)換為Excel文件,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下2022-12-12
java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實(shí)例
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實(shí)例的相關(guān)資料,希望通過本文能幫助到大家,讓大家理解掌握這部分內(nèi)容,需要的朋友可以參考下2017-10-10
Java如何將處理完異常之后的程序能夠從拋出異常的地點(diǎn)向下執(zhí)行?
今天小編就為大家分享一篇關(guān)于Java如何將處理完異常之后的程序能夠從拋出異常的地點(diǎn)向下執(zhí)行?,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-04-04
教你如何監(jiān)控 Java 線程池運(yùn)行狀態(tài)的操作(必看)
這篇文章主要介紹了教你如何監(jiān)控 Java 線程池運(yùn)行狀態(tài)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02
Java中@DateTimeFormat和@JsonFormat注解介紹
@DateTimeFormat和@JsonFormat都是處理時(shí)間格式化問題的,把其他類型轉(zhuǎn)換成自己需要的時(shí)間類型,下面這篇文章主要給大家介紹了關(guān)于Java中@DateTimeFormat和@JsonFormat注解介紹的相關(guān)資料,需要的朋友可以參考下2022-11-11

