線程的狀態詳解
1. 前言
本節內容主要是對多線程的 6 種狀態進行詳細講解,具體內容點如下:
- 拋開語言,談操作系統的線程的生命周期及線程 5 種狀態,這是我們學習 Java 多線程 6 種狀態的基礎;
- 掌握 Java 的線程生命周期及 6 種線程狀態,是我們本節課程的重點內容;
- 理解 Java 線程 6 種狀態的定義,并且通過代碼實例進行實戰演練,更深入的掌握線程的 6 種不同狀態,是我們本節內容的核心知識;
- 掌握 Java 線程不同狀態之間的轉變關系,更好地理解線程的不同狀態,是我們本節課程的重點。
2. 操作系統線程的生命周期
定義:當線程被創建并啟動以后,它既不是一啟動就進入了執行狀態,也不是一直處于執行狀態。在線程的生命周期中,它要經過新建 (New)、就緒(Runnable)、運行(Running)、阻塞 (Blocked),和死亡 (Dead) 5 種狀態。
從線程的新建 (New) 到死亡 (Dead),就是線程的整個生命周期。
下面我們分別對 5 種不同的狀態進行概念解析。
新建 (New):操作系統在進程中新建一條線程,此時線程是初始化狀態。
就緒 (Runnable):就緒狀態,可以理解為隨時待命狀態,一切已準備就緒,隨時等待運行命令。
運行 (Running):CPU 進行核心調度,對已就緒狀態的線程進行任務分配,接到調度命令,進入線程運行狀態。
阻塞 (Blocked):線程鎖導致的線程阻塞狀態。共享內存區域的共享文件,當有兩個或兩個以上的線程進行非讀操作時,只允許一個線程進行操作,其他線程在第一個線程未釋放鎖之前不可進入操作,此時進入的一個線程是運行狀態,其他線程為阻塞狀態。
死亡 (Dead):線程工作結束,被操作系統回收。
3. Java 的線程的生命周期及狀態
定義: 在 Java 線程的生命周期中,它要經過新建(New),運行(Running),阻塞(Blocked),等待(Waiting),超時等待(Timed_Waiting)和終止狀態(Terminal)6 種狀態。
從線程的新建(New)到終止狀態(Terminal),就是線程的整個生命周期。
Tips :與操作系統相比, Java 線程是否少了 “就緒” 狀態 ?其實 Java 線程依然有就緒狀態,只不過 Java 線程將 “就緒(Runnable)" 和 “運行(Running)” 兩種狀態統一歸結為 “運行(Running)” 狀態。
我們來看下 Java 線程的 6 種狀態的概念。
新建 (New):實現 Runnable 接口或者繼承 Thead 類可以得到一個線程類,new 一個實例出來,線程就進入了初始狀態。
運行 (Running):線程調度程序從可運行池中選擇一個線程作為當前線程時線程所處的狀態。這也是線程進入運行狀態的唯一方式。
阻塞 (Blocked):阻塞狀態是線程在進入 synchronized 關鍵字修飾的方法或者代碼塊時,由于其他線程正在執行,不能夠進入方法或者代碼塊而被阻塞的一種狀態。
等待 (Waiting):執行 wait () 方法后線程進入等待狀態,如果沒有顯示的 notify () 方法或者 notifyAll () 方法喚醒,該線程會一直處于等待狀態。
超時等待 (Timed_Waiting):執行 sleep(Long time)方法后,線程進入超時等待狀態,時間一到,自動喚醒線程。
終止狀態 (Terminal):當線程的 run () 方法完成時,或者主線程的 main () 方法完成時,我們就認為它終止了。這個線程對象也許是活的,但是,它已經不是一個單獨執行的線程。線程一旦終止了,就不能復生。
4. 新建(New)狀態詳解
實例:
public class ThreadTest implements Runnable{
@Override
public void run() {
System.out.println("線程:"+Thread.currentThread()+" 正在執行...");
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new ThreadTest()); //線程 創建(NEW)狀態
}
}
這里僅僅對線程進行了創建,沒有執行其他方法。 此時線程的狀態就是新建 (New) 狀態。
Tips:新建(New)狀態的線程,是沒有執行 start () 方法的線程。
5. 運行(Running)狀態詳解
定義: 線程調度程序從可運行池中選擇一個線程作為當前線程時線程所處的狀態。這也是線程進入運行狀態的唯一方式。
public class ThreadTest implements Runnable{
.......
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new ThreadTest()); //線程 創建(NEW)狀態
t1. start(); //線程進入 運行(Running)狀態
}
}
當線程調用 start () 方法后,線程才進入了運行(Running)狀態。
6. 阻塞(Blocked)狀態詳解
定義: 阻塞狀態是線程阻塞在進入 synchronized 關鍵字修飾的方法或者代碼塊時的狀態。
我們先來分析如下代碼。
實例:
public class DemoTest implements Runnable{
@Override
public void run() {
testBolockStatus();
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new DemoTest()); //線程 t1創建(NEW)狀態
t1.setName("T-one");
Thread t2 = new Thread(new DemoTest()); //線程 t2創建(NEW)狀態
t2.setName("T-two");
t1. start(); //線程 t1 進入 運行(Running)狀態
t2. start(); //線程 t2 進入 運行(Running)狀態
}
public static synchronized void testBolockStatus(){ // 該方法被 synchronized修飾
System.out.println("我是被 synchronized 修飾的同步方法, 正在有線程" +
Thread.currentThread().getName() +
"執行我,其他線程進入阻塞狀態排隊。");
}
}
代碼分析:
首先,請看關鍵代碼:
t1. start(); //線程 t1 進入 運行(Running)狀態
t2. start(); //線程 t2 進入 運行(Running)狀態
我們將線程 t1 和 t2 進行 運行狀態的啟動,此時 t1 和 t2 就會執行 run () 方法下的 sync testBolockStatus () 方法。
然后,請看關鍵代碼:
public static synchronized void testBolockStatus(){ // 該方法被 synchronized修飾
testBolockStatus () 方法是被 synchronized 修飾的同步方法。當有 2 條或者 2 條以上的線程執行該方法時, 除了進入方法的一條線程外,其他線程均處于 “阻塞” 狀態。
最后,我們看下執行結果:
我是被 synchronized 修飾的同步方法, 正在有線程T-one執行我,其他線程進入阻塞狀態排隊。
我是被 synchronized 修飾的同步方法, 正在有線程T-two執行我,其他線程進入阻塞狀態排隊。
執行結果解析:我們有兩條線程, 線程名稱分別為: T-one 和 T-two。
- 執行結果第一條: T-one 的狀態當時為 運行(Running)狀態,T-two 狀態為 阻塞(Blocked)狀態;
- 執行結果第二條: T-two 的狀態當時為 運行(Running)狀態,T-one 狀態為 阻塞(Blocked)狀態。
7. 等待(Waiting)狀態詳解
定義: 執行 wait () 方法后線程進入等待狀態,如果沒有顯示的 notify () 方法或者 notifyAll () 方法喚醒,該線程會一直處于等待狀態。
我們通過代碼來看下,等待(Waiting)狀態。
實例:
public class DemoTest implements Runnable{
@Override
public void run() {
try {
testBolockStatus();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new DemoTest()); //線程 t1創建(NEW)狀態
t1.setName("T-one");
t1. start(); //線程進入 運行 狀態
}
public synchronized void testBolockStatus() throws InterruptedException {
System.out.println("我是線程:" + Thread.currentThread().getName() + ". 我進來了。");
this.wait(); //線程進入 等待狀態 ,沒有其他線程 喚醒, 會一直等待下去
System.out.println("我是被 synchronized 修飾的同步方法, 正在有線程" +
Thread.currentThread().getName() +
"執行我,其他線程進入阻塞狀態排隊。");
}
}
注意看下關鍵代碼:
this.wait(); //線程進入 等待狀態 ,沒有其他線程 喚醒, 會一直等待下去
這里調用了 wait () 方法。線程進入 等待(Waiting)狀態。如果沒有其他線程喚醒,會一直維持等待狀態。
運行結果:
我是線程:T-one. 我進來了。
沒有辦法打印 wait () 方法后邊的執行語句,因為線程已經進入了等待狀態。
8. 超時等待(Timed-Waiting)狀態詳解
定義: 執行 sleep(Long time)方法后,線程進入超時等待狀態,時間一到,自動喚醒線程。
我們通過代碼來看下,超時等待(Timed-Waiting)狀態。
實例:
public class DemoTest implements Runnable{
@Override
public void run() {
.....
}
public static void main(String[] args) throws InterruptedException {
.....
}
public synchronized void testBolockStatus() throws InterruptedException {
System.out.println("我是線程:" + Thread.currentThread().getName() + ". 我進來了。");
Thread.sleep(5000); //超時等待 狀態 5 秒后自動喚醒線程。
System.out.println("我是被 synchronized 修飾的同步方法, 正在有線程" +
Thread.currentThread().getName() +
"執行我,其他線程進入阻塞狀態排隊。");
}
}
注意看下關鍵代碼:
Thread.sleep(5000); //超時等待 狀態 5 秒后自動喚醒線程。
這里調用了 sleep () 方法。線程進入超時等待(Timed-Waiting)狀態。超時等待時間結束,自動喚醒線程繼續執行。
運行結果:5 秒后,打印第二條語句。
我是線程:T-one. 我進來了。
我睡醒了。我是被 synchronized 修飾的同步方法, 正在有線程T-one執行我,其他線程進入阻塞狀態排隊。
9. 終止(Terminal)狀態定義
定義: 當線程的 run () 方法完成時,或者主線程的 main () 方法完成時,我們就認為它終止了。這個線程對象也許是活的,但是,它已經不是一個單獨執行的線程。線程一旦終止了,就不能復生。
10. 小結
本節的重中之重在于線程的 6 種不同的狀態,本節所有的內容都圍繞這 6 種不同的狀態進行的講解,這也是本小節的核心內容,也是必須要掌握的內容。