您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關Java中線程的生命周期有哪些,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
在「JDK1.2之后」,Java線程模型已經確定了基于操作系統原生線程模型實現。因此,目前或者今后的JDK版本中,操作系統支持怎么樣的線程模型,在很大程度上決定了Java虛擬機的線程如何映射,這一點在不同的平臺上沒有辦法達成一致,虛擬機規范中也未限定Java線程需要使用哪種線程模型來實現。線程模型只對線程的并發規模和操作成本產生影響,對于Java程序來說,這些差異是透明的。
對應Oracle Sun JDK
或者說Oracle Sun JVM
而言,它的Windows版本和Linux版本都是使用「一對一的線程模型」實現的(如下圖所示)。
也就是一條Java
線程就映射到一條輕量級進程(「Light Weight Process」)中,而一條輕量級線程又映射到一條內核線程(「Kernel-Level Thread」)。我們平時所說的線程,往往就是指輕量級進程(或者通俗來說我們平時新建的java.lang.Thread
就是輕量級進程實例的一個"句柄",因為一個java.lang.Thread
實例會對應JVM
里面的一個JavaThread
實例,而JVM
里面的JavaThread
就應該理解為輕量級進程)。前面推算這個線程映射關系,可以知道,我們在應用程序中創建或者操作的java.lang.Thread
實例最終會映射到系統的內核線程,如果我們惡意或者實驗性無限創建java.lang.Thread
實例,最終會影響系統的正常運行甚至導致系統崩潰(可以在Windows
開發環境中做實驗,確保內存足夠的情況下使用死循環創建和運行java.lang.Thread
實例)。
線程調度方式包括兩種,協同式線程調度和搶占式線程調度。
線程調度方式 | 描述 | 劣勢 | 優勢 |
---|---|---|---|
協同式線程調度 | 線程的執行時間由線程本身控制,執行完畢后主動通知操作系統切換到另一個線程上 | 某個線程如果不讓出CPU執行時間可能會導致整個系統崩潰 | 實現簡單,沒有線程同步的問題 |
搶占式線程調度 | 每個線程由操作系統來分配執行時間,線程的切換不由線程自身決定 | 實現相對復雜,操作系統需要控制線程同步和切換 | 不會出現一個線程阻塞導致系統崩潰的問題 |
Java
線程最終會映射為系統內核原生線程,所以Java
線程調度最終取決于系操作系統,而目前主流的操作系統內核線程調度基本都是使用搶占式線程調度。也就是可以死記硬背一下:「Java線程是使用搶占式線程調度方式進行線程調度的」。
很多操作系統都提供線程優先級的概念,但是由于平臺特性的問題,Java中的線程優先級和不同平臺中系統線程優先級并不匹配,所以Java線程優先級可以僅僅理解為“「建議優先級」”,通俗來說就是java.lang.Thread#setPriority(int newPriority)
并不一定生效,「有可能Java線程的優先級會被系統自行改變」。
Java
線程的狀態可以從java.lang.Thread
的內部枚舉類java.lang.Thread$State
得知:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
這些狀態的描述總結成圖如下:
「線程狀態之間關系切換」圖如下:
下面通過API注釋和一些簡單的代碼例子分析一下Java線程的狀態含義和狀態切換。
「API注釋」:
/**
* Thread state for a thread which has not yet started.
*
*/
NEW,
?線程實例尚未啟動時候的線程狀態。
?
一個剛創建而尚未啟動(尚未調用Thread#start()
方法)的Java線程實例的就是處于NEW
狀態。
public class ThreadState {
public static void main(String[] args) throws Exception {
Thread thread = new Thread();
System.out.println(thread.getState());
}
}
// 輸出結果
NEW
「API注釋」:
/**
* 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,
?可運行狀態下線程的線程狀態。可運行狀態下的線程在Java虛擬機中執行,但它可能執行等待操作系統的其他資源,例如處理器。
?
當Java線程實例調用了Thread#start()
之后,就會進入RUNNABLE
狀態。RUNNABLE
狀態可以認為包含兩個子狀態:READY
和RUNNING
。
READY
:該狀態的線程可以被線程調度器進行調度使之更變為
RUNNING
狀態。RUNNING
:該狀態表示線程正在運行,線程對象的
run()
方法中的代碼所對應的的指令正在被CPU執行。當Java線程實例Thread#yield()
方法被調用時或者由于線程調度器的調度,線程實例的狀態有可能由RUNNING
轉變為READY
,但是從線程狀態Thread#getState()
獲取到的狀態依然是RUNNABLE
。例如:
public class ThreadState1 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(()-> {
while (true){
Thread.yield();
}
});
thread.start();
Thread.sleep(2000);
System.out.println(thread.getState());
}
}
// 輸出結果
RUNNABLE
「API注釋」:
/**
* 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 {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
?等待中線程的狀態。一個線程進入等待狀態是由于調用了下面方法之一:不帶超時的Object#wait() 不帶超時的Thread#join() LockSupport.park() 一個處于等待狀態的線程總是在等待另一個線程進行一些特殊的處理。例如:一個線程調用了Object#wait(),那么它在等待另一個線程調用對象上的Object#notify()或者Object#notifyAll();一個線程調用了Thread#join(),那么它在等待另一個線程終結。
?
WAITING
是「無限期的等待狀態」,這種狀態下的線程不會被分配CPU執行時間。當一個線程執行了某些方法之后就會進入無限期等待狀態,直到被顯式喚醒,被喚醒后,線程狀態由WAITING
更變為RUNNABLE
然后繼續執行。
RUNNABLE 轉換為WAITING 的方法(無限期等待) | WAITING 轉換為RUNNABLE 的方法(喚醒) |
---|---|
Object#wait() | Object#notify() | Object#notifyAll() |
Thread#join() | - |
LockSupport.part() | LockSupport.unpart(thread) |
其中Thread#join()
方法相對比較特殊,它會阻塞線程實例直到線程實例執行完畢,可以觀察它的源碼如下:
public final void join() throws InterruptedException {
join(0);
}
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;
}
}
}
可見Thread#join()
是在線程實例存活的時候總是調用Object#wait()
方法,也就是必須在線程執行完畢isAlive()
為false(意味著線程生命周期已經終結)的時候才會解除阻塞。
基于WAITING
狀態舉個例子:
public class ThreadState3 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(()-> {
LockSupport.park();
while (true){
Thread.yield();
}
});
thread.start();
Thread.sleep(50);
System.out.println(thread.getState());
LockSupport.unpark(thread);
Thread.sleep(50);
System.out.println(thread.getState());
}
}
// 輸出結果
WAITING
RUNNABLE
「API注釋」:
/**
* 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.sleep() 帶超時的Object#wait() 帶超時的Thread#join() LockSupport.parkNanos() LockSupport.parkUntil()
?
TIMED WAITING
就是「有限期等待狀態」,它和WAITING
有點相似,這種狀態下的線程不會被分配CPU執行時間,不過這種狀態下的線程不需要被顯式喚醒,只需要等待超時限期到達就會被VM
喚醒,有點類似于現實生活中的鬧鐘。
RUNNABLE 轉換為TIMED WAITING 的方法(有限期等待) | TIMED WAITING 轉換為RUNNABLE 的方法(超時解除等待) |
---|---|
Object#wait(timeout) | - |
Thread#sleep(timeout) | - |
Thread#join(timeout) | - |
LockSupport.parkNanos(timeout) | - |
LockSupport.parkUntil(timeout) | - |
舉個例子:
public class ThreadState4 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(()-> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//ignore
}
});
thread.start();
Thread.sleep(50);
System.out.println(thread.getState());
Thread.sleep(1000);
System.out.println(thread.getState());
}
}
// 輸出結果
TIMED_WAITING
TERMINATED
「API注釋」:
/**
* 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,
?此狀態表示一個線程正在阻塞等待獲取一個監視器鎖。如果線程處于阻塞狀態,說明線程等待進入同步代碼塊或者同步方法的監視器鎖或者在調用了Object#wait()之后重入同步代碼塊或者同步方法。
?
BLOCKED
狀態也就是阻塞狀態,該狀態下的線程不會被分配CPU執行時間。線程的狀態為BLOCKED
的時候有兩種可能的情況:
?A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method
?
synchronized
代碼塊或者
synchronized
方法,在此等待獲取鎖的過程線程都處于阻塞狀態。?reenter a synchronized block/method after calling Object#wait()
?
synchronized
代碼塊或者
synchronized
方法后(此時已經釋放監視器鎖)調用
Object#wait()
方法之后進行阻塞,當接收其他線程T調用該鎖對象
Object#notify()/notifyAll()
,但是線程T尚未退出它所在的
synchronized
代碼塊或者
synchronized
方法,那么線程X依然處于阻塞狀態(注意API注釋中的
「reenter」,理解它場景2就豁然開朗)。更加詳細的描述可以參考筆者之前寫過的一篇文章:深入理解Object提供的阻塞和喚醒API
針對上面的場景1舉個簡單的例子:
public class ThreadState6 {
private static final Object MONITOR = new Object();
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(()-> {
synchronized (MONITOR){
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
//ignore
}
}
});
Thread thread2 = new Thread(()-> {
synchronized (MONITOR){
System.out.println("thread2 got monitor lock...");
}
});
thread1.start();
Thread.sleep(50);
thread2.start();
Thread.sleep(50);
System.out.println(thread2.getState());
}
}
// 輸出結果
BLOCKED
針對上面的場景2舉個簡單的例子:
public class ThreadState7 {
private static final Object MONITOR = new Object();
private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws Exception {
System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now())));
Thread thread1 = new Thread(() -> {
synchronized (MONITOR) {
System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now())));
try {
Thread.sleep(1000);
MONITOR.wait();
} catch (InterruptedException e) {
//ignore
}
System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now())));
}
});
Thread thread2 = new Thread(() -> {
synchronized (MONITOR) {
System.out.println(String.format("[%s]-thread2 got monitor lock...", F.format(LocalDateTime.now())));
try {
MONITOR.notify();
Thread.sleep(2000);
} catch (InterruptedException e) {
//ignore
}
System.out.println(String.format("[%s]-thread2 releases monitor lock...", F.format(LocalDateTime.now())));
}
});
thread1.start();
thread2.start();
// 這里故意讓主線程sleep 1500毫秒從而讓thread2調用了Object#notify()并且尚未退出同步代碼塊,確保thread1調用了Object#wait()
Thread.sleep(1500);
System.out.println(thread1.getState());
System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now())));
}
}
// 某個時刻的輸出如下:
[2019-06-20 00:30:22]-begin...
[2019-06-20 00:30:22]-thread1 got monitor lock...
[2019-06-20 00:30:23]-thread2 got monitor lock...
BLOCKED
[2019-06-20 00:30:23]-end...
[2019-06-20 00:30:25]-thread2 releases monitor lock...
[2019-06-20 00:30:25]-thread1 exit waiting...
場景2中:
Object#notify()
后睡眠2000毫秒再退出同步代碼塊,釋放監視器鎖。Object#wait()
,此時它已經釋放了監視器鎖,所以線程2成功進入同步塊,線程1處于API注釋中所述的
reenter a synchronized block/method
的狀態。reenter
狀態并且打印其線程狀態,剛好就是
BLOCKED
狀態。這三點看起來有點繞,多看幾次多思考一下應該就能理解。
「API注釋」:
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
?終結的線程對應的線程狀態,此時線程已經執行完畢。
?
TERMINATED
狀態表示線程已經終結。一個線程實例只能被啟動一次,準確來說,只會調用一次Thread#run()
方法,Thread#run()
方法執行結束之后,線程狀態就會更變為TERMINATED
,意味著線程的生命周期已經結束。
舉個簡單的例子:
public class ThreadState8 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
});
thread.start();
Thread.sleep(50);
System.out.println(thread.getState());
}
}
// 輸出結果
TERMINATED
多線程環境中,當一個線程的狀態由RUNNABLE
轉換為非RUNNABLE
(BLOCKED
、WAITING
或者TIMED_WAITING
)時,相應線程的上下文信息(也就是常說的Context
,包括CPU
的寄存器和程序計數器在某一時間點的內容等等)需要被保存,以便線程稍后恢復為RUNNABLE
狀態時能夠在之前的執行進度的基礎上繼續執行。而一個線程的狀態由非RUNNABLE
狀態進入RUNNABLE
狀態時可能涉及恢復之前保存的線程上下文信息并且在此基礎上繼續執行。這里的對「線程的上下文信息進行保存和恢復的過程」就稱為上下文切換(Context Switch
)。
線程的上下文切換會帶來額外的性能開銷,這包括保存和恢復線程上下文信息的開銷、對線程進行調度的CPU
時間開銷以及CPU
緩存內容失效的開銷(線程所執行的代碼從CPU
緩存中訪問其所需要的變量值要比從主內存(RAM
)中訪問響應的變量值要快得多,但是「線程上下文切換會導致相關線程所訪問的CPU緩存內容失效,一般是CPU的L1 Cache
和L2 Cache
」,使得相關線程稍后被重新調度到運行時其不得不再次訪問主內存中的變量以重新創建CPU
緩存內容)。
在Linux
系統中,可以通過vmstat
命令來查看全局的上下文切換的次數,例如:
$ vmstat 1
對于Java
程序的運行,在Linux
系統中也可以通過perf
命令進行監視,例如:
$ perf stat -e cpu-clock,task-clock,cs,cache-reference,cache-misses java YourJavaClass
參考資料中提到Windows
系統下可以通過自帶的工具perfmon
(其實也就是任務管理器)來監視線程的上下文切換,實際上筆者并沒有從任務管理器發現有任何辦法查看上下文切換,通過搜索之后發現了一個工具:Process Explorer。運行Process Explorer
同時運行一個Java
程序并且查看其狀態:
因為打了斷點,可以看到運行中的程序的上下文切換一共7000多次,當前一秒的上下文切換增量為26(因為筆者設置了Process Explorer
每秒刷新一次數據)。
如果項目在生產環境中運行,不可能頻繁調用Thread#getState()
方法去監測線程的狀態變化。JDK本身提供了一些監控線程狀態的工具,還有一些開源的輕量級工具如阿里的Arthas,這里簡單介紹一下。
jvisualvm
是JDK自帶的堆、線程等待JVM指標監控工具,適合使用于開發和測試環境。它位于JAVA_HOME/bin
目錄之下。
其中線程Dump
的按鈕類似于下面要提到的jstack
命令,用于導出所有線程的棧信息。
jstack
是JDK自帶的命令行工具,功能是用于獲取指定PID的Java進程的線程棧信息。例如本地運行的一個IDEA
實例的PID
是11376,那么只需要輸入:
jstack 11376
然后控制臺輸出如下:
另外,如果想要定位具體Java進程的PID
,可以使用jps
命令。
JMC
也就是Java Mission Control
,它也是JDK自帶的工具,提供的功能要比jvisualvm
強大,包括MBean的處理、線程棧已經狀態查看、飛行記錄器等等。
以上就是Java中線程的生命周期有哪些,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。