(手機橫屏看源碼更方便) 註:java源碼分析部分如無特殊說明均基於 java8 版本。 簡介 大家都知道線程是有生命周期,但是彤哥可以認真負責地告訴你網上幾乎沒有一篇文章講得是完全正確的。 常見的錯誤 有:就緒狀態、運行中狀態(RUNNING)、死亡狀態、中斷狀態、只有阻塞沒有等待狀態、流程圖亂畫 ...
(手機橫屏看源碼更方便)
註:java源碼分析部分如無特殊說明均基於 java8 版本。
簡介
大家都知道線程是有生命周期,但是彤哥可以認真負責地告訴你網上幾乎沒有一篇文章講得是完全正確的。
常見的錯誤有:就緒狀態、運行中狀態(RUNNING)、死亡狀態、中斷狀態、只有阻塞沒有等待狀態、流程圖亂畫等,最常見的錯誤就是說線程只有5種狀態。
今天這篇文章會徹底講清楚線程的生命周期,並分析synchronized鎖、基於AQS的鎖中線程狀態變化的邏輯。
所以,對synchronized鎖和AQS原理(源碼)不瞭解的同學,請翻一下彤哥之前的文章先熟悉這兩部分的內容,否則肯定記不住這裡講的線程生命周期。
問題
(1)線程的狀態有哪些?
(2)synchronized鎖各階段線程處於什麼狀態?
(3)重入鎖、條件鎖各階段線程處於什麼狀態?
先上源碼
關於線程的生命周期,我們可以看一下java.lang.Thread.State
這個類,它是線程的內部枚舉類,定義了線程的各種狀態,並且註釋也很清晰。
public enum State {
/**
* 新建狀態,線程還未開始
*/
NEW,
/**
* 可運行狀態,正在運行或者在等待系統資源,比如CPU
*/
RUNNABLE,
/**
* 阻塞狀態,在等待一個監視器鎖(也就是我們常說的synchronized)
* 或者在調用了Object.wait()方法且被notify()之後也會進入BLOCKED狀態
*/
BLOCKED,
/**
* 等待狀態,在調用了以下方法後進入此狀態
* 1. Object.wait()無超時的方法後且未被notify()前,如果被notify()了會進入BLOCKED狀態
* 2. Thread.join()無超時的方法後
* 3. LockSupport.park()無超時的方法後
*/
WAITING,
/**
* 超時等待狀態,在調用了以下方法後會進入超時等待狀態
* 1. Thread.sleep()方法後【本文由公從號“彤哥讀源碼”原創】
* 2. Object.wait(timeout)方法後且未到超時時間前,如果達到超時了或被notify()了會進入BLOCKED狀態
* 3. Thread.join(timeout)方法後
* 4. LockSupport.parkNanos(nanos)方法後
* 5. LockSupport.parkUntil(deadline)方法後
*/
TIMED_WAITING,
/**
* 終止狀態,線程已經執行完畢
*/
TERMINATED;
}
流程圖
線程生命周期中各狀態的註釋完畢了,下麵我們再來看看各狀態之間的流轉:
怎麼樣?是不是很複雜?彤哥幾乎把網上的資料都查了一遍,沒有一篇文章把這個流程圖完整畫出來的,下麵彤哥就來一一解釋:
(1)為了方便講解,我們把鎖分成兩大類,一類是synchronized鎖,一類是基於AQS的鎖(我們拿重入鎖舉例),也就是內部使用了LockSupport.park()/parkNanos()/parkUntil()幾個方法的鎖;
(2)不管是synchronized鎖還是基於AQS的鎖,內部都是分成兩個隊列,一個是同步隊列(AQS的隊列),一個是等待隊列(Condition的隊列);
(3)對於內部調用了object.wait()/wait(timeout)或者condition.await()/await(timeout)方法,線程都是先進入等待隊列,被notify()/signal()或者超時後,才會進入同步隊列;
(4)明確聲明,BLOCKED狀態只有線程處於synchronized的同步隊列的時候才會有這個狀態,其它任何情況都跟這個狀態無關;
(5)對於synchronized,線程執行synchronized的時候,如果立即獲得了鎖(沒有進入同步隊列),線程處於RUNNABLE狀態;
(6)對於synchronized,線程執行synchronized的時候,如果無法獲得鎖(直接進入同步隊列),線程處於BLOCKED狀態;
(5)對於synchronized內部,調用了object.wait()之後線程處於WAITING狀態(進入等待隊列);
(6)對於synchronized內部,調用了object.wait(timeout)之後線程處於TIMED_WAITING狀態(進入等待隊列);
(7)對於synchronized內部,調用了object.wait()之後且被notify()了,如果線程立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;
(8)對於synchronized內部,調用了object.wait(timeout)之後且被notify()了,如果線程立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;
(9)對於synchronized內部,調用了object.wait(timeout)之後且超時了,這時如果線程正好立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;
(10)對於synchronized內部,調用了object.wait()之後且被notify()了,如果線程無法獲得鎖(也就是進入了同步隊列),線程處於BLOCKED狀態;
(11)對於synchronized內部,調用了object.wait(timeout)之後且被notify()了或者超時了,如果線程無法獲得鎖(也就是進入了同步隊列),線程處於BLOCKED狀態;
(12)對於重入鎖,線程執行lock.lock()的時候,如果立即獲得了鎖(沒有進入同步隊列),線程處於RUNNABLE狀態;
(13)對於重入鎖,線程執行lock.lock()的時候,如果無法獲得鎖(直接進入同步隊列),線程處於WAITING狀態;
(14)對於重入鎖內部,調用了condition.await()之後線程處於WAITING狀態(進入等待隊列);
(15)對於重入鎖內部,調用了condition.await(timeout)之後線程處於TIMED_WAITING狀態(進入等待隊列);
(16)對於重入鎖內部,調用了condition.await()之後且被signal()了,如果線程立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;
(17)對於重入鎖內部,調用了condition.await(timeout)之後且被signal()了,如果線程立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;
(18)對於重入鎖內部,調用了condition.await(timeout)之後且超時了,這時如果線程正好立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;
(19)對於重入鎖內部,調用了condition.await()之後且被signal()了,如果線程無法獲得鎖(也就是進入了同步隊列),線程處於WAITING狀態;
(20)對於重入鎖內部,調用了condition.await(timeout)之後且被signal()了或者超時了,如果線程無法獲得鎖(也就是進入了同步隊列),線程處於WAITING狀態;
(21)對於重入鎖,如果內部調用了condition.await()之後且被signal()之後依然無法獲取鎖的,其實經歷了兩次WAITING狀態的切換,一次是在等待隊列,一次是在同步隊列;
(22)對於重入鎖,如果內部調用了condition.await(timeout)之後且被signal()或超時了的,狀態會有一個從TIMED_WAITING切換到WAITING的過程,也就是從等待隊列進入到同步隊列;
為了便於理解,彤哥這裡每一條都分的比較細,麻煩耐心看完。
測試用例
看完上面的部分,你肯定想知道怎麼去驗證,下麵彤哥就說說驗證的方法,先給出測試用例。
public class ThreadLifeTest {
public static void main(String[] args) throws IOException {
Object object = new Object();
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
synchronized (object) {
try {
System.out.println("thread1 waiting");
object.wait();
// object.wait(5000);
System.out.println("thread1 after waiting");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread1").start();
new Thread(()->{
synchronized (object) {
try {
System.out.println("thread2 notify");
// 打開或關閉這段註釋,觀察Thread1的狀態
// object.notify();【本文由公從號“彤哥讀源碼”原創】
// notify之後當前線程並不會釋放鎖,只是被notify的線程從等待隊列進入同步隊列
// sleep也不會釋放鎖
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread2").start();
new Thread(()->{
lock.lock();
System.out.println("thread3 waiting");
try {
condition.await();
// condition.await(200, (TimeUnit).SECONDS);
System.out.println("thread3 after waiting");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "Thread3").start();
new Thread(()->{
lock.lock();
System.out.println("thread4");
// 打開或關閉這段註釋,觀察Thread3的狀態
// condition.signal();【本文由公從號“彤哥讀源碼”原創】
// signal之後當前線程並不會釋放鎖,只是被signal的線程從等待隊列進入同步隊列
// sleep也不會釋放鎖
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "Thread4").start();
}
}
打開或關閉上面註釋部分的代碼,使用IDEA的RUN模式運行代碼,然後點擊左邊的一個攝像頭按鈕(jstack),查看各線程的狀態。
註:不要使用DEBUG模式,DEBUG模式全都變成WAITING狀態了,很神奇。
彩蛋
其實,本來這篇是準備寫線程池的生命周期的,奈何線程的生命周期寫了太多,等下一篇我們再來一起學習線程池的生命周期吧。
歡迎關註我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。