深入了解多線程(三)
在前兩篇部落格中深入了解多線程(一)、深入了解多線程(二)中分别介紹了:多線程在jvm記憶體圖中的狀态、多線程的兩種實作方式、多線程的安全;接下來介紹線程的狀态。
1 線程狀态概述
當線程被建立并啟動以後,它既不是一啟動就進入了執行狀态,也不是一直處于執行狀态。線上程的生命周期中,
有幾種狀态呢?在API中 java.lang.Thread.State 這個枚舉中給出了六種線程狀态:
這裡先列出各個線程狀态發生的條件,下面将會對每種狀态進行詳細解析
線程狀态 | 導緻狀态發生條件 |
---|---|
New(建立) | 線程剛被建立,但是并未啟動。還沒調用start方法 |
Runable(可運作) | 線程可以在java虛拟機中運作的狀态,可能正在運作自己代碼,也可能沒有,這取決于作業系統處理器 |
Blocked(鎖阻塞) | 當一個線程試圖擷取一個對象鎖,而該對象鎖被其他的線程持有,則該線程進入Blocked狀态;當該線程持有鎖時,該線程将變成Runable狀态 |
Waiting(無限等待) | 一個線程在等待另一個線程執行一個(喚醒)動作時,該線程進入Waiting狀态。進入這個 狀态後是不能自動喚醒的,必須等待另一個線程調用notify或者notifyAll方法才能夠喚醒 |
Timed Waiting(計時等待) | 同waiting狀态,有幾個方法有逾時參數,調用他們将進入Timed Waiting 狀态。這一狀态将一直保持到朝時期滿或者接受到喚醒通知。帶有逾時參數的常用方法有Thread.sleep、Object.wait |
Teminated(被終止) | 因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡 |
這裡不再研究這幾種狀态的實作原理,而是研究這幾種狀态的轉換問題。
1.1 Timed Waiting(計時等待)
這裡的計時等待,其實是顯而易見的,我們平常寫代碼的時候也經常用到,就是Thread.sleep(),其實 當我們調用了sleep方法後,目前執行的線程就進入到“休眠狀态”,其實就是所謂的Timed Waiting ,接下來将通過一個案例加深對該狀态的一個了解。
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i:"+i);
}
}
}
通過案例可以發現,sleep方法的使用還是很簡單的。我們需要記住下面幾點:
- 進入 TIMED_WAITING 狀态的一種常見情形是調用的 sleep 方法,單獨的線程也可以調用,不一定非要有協
作關系。
- 為了讓其他線程有機會執行,可以将Thread.sleep()的調用放線程run()之内。這樣才能保證該線程執行過程
中會睡眠
- sleep與鎖無關,線程睡眠到期自動蘇醒,并傳回到Runnable(可運作)狀态。
小提示:sleep()中指定的時間是線程不會運作的最短時間。是以,sleep()方法不能保證該線程睡眠到期後就開始立刻執行。
Timed Waiting 線程狀态圖:
1.2 BLOCKED(鎖狀态)
Blocked狀态在API中的介紹為:一個正在阻塞等待一個螢幕鎖(鎖對象)的線程處于這一狀态。
知道了多線程的同步機制,那麼這個狀态是非常好了解的了。比如,線程A與線程B代碼中使用同一鎖,如果線程A獲 取到鎖,線程A進入到Runnable狀态,那麼線程B就進入到Blocked鎖阻塞狀态。
這是由Runnable狀态進入Blocked狀态。除此Waiting以及Time Waiting狀态也會在某種情況下進入阻塞狀态
1.3 Waiting (無限等待)
Wating狀态在API中介紹為:一個正在無限期等待另一個線程執行一個特别的(喚醒)動作的線程處于這一狀态。
無限等待一般情況下是很少遇到的,但并不妨礙我們進行一個簡單深入的了解。我們通過一段代碼來學習一下:
public class MyThread{
public static Object object=new Object();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (object){
System.out.println(Thread.currentThread().getName()+"擷取到鎖對象,調用wait方法,進入waiting,釋放鎖對象");
try {
//無限等待
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"從waiting狀态醒來,擷取鎖對象,繼續執行");
}
}
}
},"等待").start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+"等待3秒鐘");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object){
System.out.println(Thread.currentThread().getName()+"擷取鎖對象,調用notify方法,釋放鎖對象");
object.notify();
}
}
}
},"喚醒").start();
}
}
通過上述案例我們會發現,一個調用了某個對象的 Object.wait 方法的線程會等待另一個線程調用此對象的
Object.notify()方法 或 Object.notifyAll()方法。
其實waiting狀态并不是一個線程的操作,它展現的是多個線程間的通信,可以了解為多個線程之間的協作關系,
多個線程會争取鎖,同時互相之間又存在協作關系。就好比在公司裡你和你的同僚們,你們可能存在晉升時的競
争,但更多時候你們更多是一起合作以完成某些任務。
當多個線程協作時,比如A,B線程,如果A線程在Runnable(可運作)狀态中調用了wait()方法那麼A線程就進入
了Waiting(無限等待)狀态,同時失去了同步鎖。假如這個時候B線程擷取到了同步鎖,在運作狀态中調用了
notify()方法,那麼就會将無限等待的A線程喚醒。注意是喚醒,如果擷取到鎖對象,那麼A線程喚醒後就進入
Runnable(可運作)狀态;如果沒有擷取鎖對象,那麼就進入到Blocked(鎖阻塞狀态)。
總結:線程狀态轉換圖
Timed Waiting(計時等待) 與 Waiting(無限等待) 狀态聯系還是很緊密的,
比如Waiting(無限等待) 狀态中wait方法是空參的,而timed waiting(計時等待) 中wait方法是帶參的。
這種帶參的方法,其實是一種倒計時操作,相當于我們生活中的小鬧鐘,我們設定好時間,到時通知,可是 如果提前得到(喚醒)通知,那麼設定好時間在通知也就顯得多此一舉了,那麼這種設計方案其實是一舉兩得。如果沒有得到(喚醒)通知,那麼線程就處于Timed Waiting狀态,直到倒計時完畢自動醒來;如果在倒計時期間得到(喚醒)通知,那麼線程從Timed Waiting狀态立刻喚醒。