最開始的打算是隻寫線程的狀態及轉換的(即下文中的 一、2 點),由於在看了 Thread 源碼和別人寫的關於線程狀態轉換的博客以後,內心挺明瞭的,以爲本身應該理解了。。。html
不過在學 AQS 的時候發現用到了不少 LockSupport.park() 和 LockSupport.unpark(),涉及到掛起線程與喚醒線程,這個時候才意識到本身對於這一塊其實並非太熟,並不太清楚 LockSupport.park() 和 LockSupport.unpark() 是怎麼用的(只知道一個會掛起線程,另外一個會喚醒線程,可是其中的原理並不清楚),因而又跑去看 LockSupport 的源碼、學習 Object.wait() 和 Object.notify() 的使用及原理,此時才以爲對線程狀態的轉換纔算是入門了。java
切實體會到了紙上談兵是多麼的好笑!好了,進入正題。git
線程都有哪些狀態呢?它們之間是如何轉換的呢?如何經過代碼來完成這些轉換呢?這就是這篇文章要解決的問題。github
其實以前對線程的狀態仍是瞭解過的,不過沒有系統的整理過,有些知識點仍是略微模糊的。例如:oracle
Java 線程(爲何說是 Java 線程呢?由於操做系統線程狀態和這 6 個狀態還有些許差別)一共有 6 種狀態,Thread 類中專門定義了一個枚舉類 State 來表示這 6 種狀態。學習
建立了一個 Thread 實例,尚未調用其 start() 方法,此時線程處於新建狀態。ui
運行狀態包含了 Ready 和 Running 兩種狀態。什麼意思呢?spa
Ready 狀態的意思就是我已經準備好一切,只要讓我用 CPU 我就能 Running,即正在等待 CPU 分配執行時間;操作系統
Running 狀態就是獲得了 CPU 的執行時間,正在運行。.net
線程 A 進入同步區域的時候,須要獲取一個排他鎖,若此時排他鎖正在被另外一個線程 B 佔有,線程 A 將進入阻塞狀態。
處於這種狀態的線程不會被分配 CPU 執行時間,它們要等待被其餘線程顯式地喚醒。如下方法會讓線程陷入無限期的等待狀態:
處於這種狀態的線程也不會被分配 CPU 執行時間,不過無須等待被其餘線程顯式地喚醒,在必定時間以後它們會由系統自動喚醒。如下方法會讓線程進入限期等待狀態:
run() 方法執行完成,線程結束執行,此時的線程狀態。
上一節講了線程的 6 個狀態以及線程進入這 6 個狀態的場景,其實不是很直觀;下面的這幅圖很直觀的展現了線程狀態的轉換(建議本身去畫一遍這個圖,若是能不借助外物就能畫出來的話,基本上線程狀態轉換也就沒什麼問題了)。
我我的以爲這圖還挺好看的!不知諸位以爲如何?
private static void newState() {
Thread newThread = new Thread("00-newThread");
System.out.println(newThread.getState());
}
複製代碼
private static void runnableState() {
Thread runnableThread = new Thread(() -> {
while (true) {
}
}, "01-runnableThread");
runnableThread.start();
System.out.println(runnableThread.getState());
}
複製代碼
private static void blockedState() {
Thread holdLockThread = new Thread(() -> {
synchronized (lock) {
while (true) {
}
}
}, "holdLockThread");
holdLockThread.start();
Thread blockedThread = new Thread(() -> {
synchronized (lock) {
while (true) {
}
}
}, "04-blockedThread");
blockedThread.start();
System.out.println(blockedThread.getState());
}
複製代碼
private static void waitingState() {
Thread waitingThread = new Thread(() -> {
LockSupport.park();
}, "02-waitingThread");
waitingThread.start();
System.out.println(waitingThread.getState());
}
複製代碼
private static void timeWaitingState() {
Thread timeWaitingThread = new Thread(() -> {
LockSupport.parkUntil(LocalDateTime.now().plusDays(1).toEpochSecond(ZoneOffset.of("+8")) * 1000);
}, "03-timeWaitingThread");
timeWaitingThread.start();
System.out.println(timeWaitingThread.getState());
}
複製代碼
private static void terminatedState() {
Thread terminatedThread = new Thread("05-terminatedThread");
terminatedThread.start();
try {
terminatedThread.join();// 等待 terminatedThread 執行完成
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(terminatedThread.getState());
}
複製代碼
private static void waitAndNotify() {
Thread waitThread = new Thread(() -> {
synchronized (waitAndNotify) {
System.out.println("I'm wait thread.");
try {
System.out.println("waiting...");
waitAndNotify.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("notified.");
}
}, "waitThread");
Thread notifyThread = new Thread(() -> {
synchronized (waitAndNotify) {
System.out.println("I'm notified thread.");
waitAndNotify.notify();
System.out.println("notify wait thread.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after sleep 1 second.");
}
}, "notifyThread");
waitThread.start();
try {
Thread.sleep(20);// 確保 waitThread 在 notifyThread 以前執行
} catch (InterruptedException e) {
e.printStackTrace();
}
notifyThread.start();
}
複製代碼
輸出:
I'm wait thread.
waiting...
I'm notified thread.
notify wait thread.
after sleep 1 second.
notified.
複製代碼
關於 Object.wait() 和 Object.notify() 補充幾點:
(1)當前線程必須是 waitAndNotify monitor 的持有者,若是不是會拋 IllegalMonitorStateException;
(2)調用 waitAndNotify.wait() 方法會將當前線程放入 waitAndNotify 對象的 wait set
中等待被喚醒;而且釋放其持有的全部鎖;
(3)調用 waitAndNotify.notify() 方法會從 waitAndNotify 對象的 wait set
中喚醒一個線程(此例中的 waitThread),不過 waitThread 不會立刻執行,它必須等待 notifyThread 釋放 waitAndNotify 鎖;當 waitThread 再次得到 waitAndNotify 鎖,才能夠再次執行。也就解釋了爲何 notified.
會在最後輸出。
private static void parkAndUnpark() {
Thread parkThread = new Thread(() -> {
System.out.println("park.");
LockSupport.park();
System.out.println("unpark.");
}, "parkThread");
parkThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after sleep 1 second");
LockSupport.unpark(parkThread);
}
複製代碼
輸出:
park.
after sleep 1 second
unpark.
複製代碼
本文差很少結束了,完整代碼可在這裏找到。不過遺留的問題其實還有不少:
遺留的這些問題基本上都是一些 native 方法,須要跟蹤 C++ 源碼;或者查看官方文檔,好比: Threads and Locks。
通過查閱資料以及對 C++ 源碼的摸索,對於上面幾個問題背後的原理我也正在理解中,後續我會將本身的一些理解整理出來。
我一直堅信,鑽研技術要作到:知其然,知其因此然!
參考資料:
(2)《深刻理解 Java 虛擬機》第二版.