前言:執行緒的五種狀態
本文是執行緒篇的一個分支,主要結合我的理解,看一下sleep和wait以及執行緒的一些狀態
網上的圖看起來都有點醜,我自己畫了一幅:
1.New: 新建態: new Thread ~ thread.start期間
2.Runnable: 可執行態: 可被CPU排程執行期間。
3.Running 執行態: 執行緒獲取CPU許可權進行執行
4.Blocked 阻塞狀態: 阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。
|---等待阻塞:通過呼叫執行緒的wait()方法,讓執行緒等待某工作的完成。
|---同步阻塞:執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用)時
|---其他阻塞:通過呼叫執行緒的sleep()或join()或發出了I/O請求時
5.Dead 死亡狀態: 執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。
複製程式碼
一、Thread.sleep簡述
暫停當前執行緒,進入Blocked狀態,把cpu片段讓出給其他執行緒。
1.測試程式碼:
public class Main0 {
static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
public static void main(String[] args) {
new Thread(new Car()).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Ambulance()).start();
}
private static class Car implements Runnable {
@Override
public void run() {
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
}
}
private static class Ambulance implements Runnable {
@Override
public void run() {
System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車開始啟動,在路上跑");
System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車跑到終點");
}
}
}
複製程式碼
2. 結果分析:注02:29代表當前時刻的分秒,即2分29秒
---->[執行結果]----------------------
02:29:小汽車開始啟動,在路上跑
02:29:小汽車跑到終點
02:31:救護車開始啟動,在路上跑
02:31:救護車跑到終點
複製程式碼
二、執行緒內sleep以及synchronized
1.子執行緒加入休眠
private static class Car implements Runnable {
@Override
public void run() {
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
}
}
複製程式碼
2.結果分析
Car執行緒的任務是睡5s,可見主執行緒的休眠沒有影響到Car子執行緒的執行(休眠)
18:48:小汽車開始啟動,在路上跑
18:50:救護車開始啟動,在路上跑
18:50:救護車跑到終點
18:53:小汽車跑到終點
複製程式碼
3.當加鎖睡眠時
線上程1中加synchronized(
這裡鎖用sdf物件,你也可以任意
)
public class Main2 {
static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
public static void main(String[] args) {
new Thread(new Car()).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Ambulance()).start();
}
private static class Car implements Runnable {
@Override
public void run() {
synchronized (sdf){
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
}
}
}
private static class Ambulance implements Runnable {
@Override
public void run() {
synchronized (sdf){
System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車開始啟動,在路上跑");
System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車跑到終點");
}
}
}
}
複製程式碼
2.結果分析
Car執行緒和Ambulance執行緒在執行時使用同一把鎖,執行緒在休眠時不會釋放鎖
所以Ambulance執行緒需要等待Car執行緒執行完成,才能進行執行
23:46:小汽車開始啟動,在路上跑
23:51:小汽車跑到終點
23:51:救護車開始啟動,在路上跑
23:51:救護車跑到終點
複製程式碼
三、Object#wait()
方法的作用
在
執行緒t1
呼叫A物件
的wait()方法
,會釋放t1持有的鎖
,讓t1進入等待佇列(Blocked狀態)
直到其他執行緒呼叫A物件的notify()方法
或notifyAll()
方法t1進入同步佇列(Blocked狀態)
當t1獲得鎖後會進入就緒狀態Runnable
,獲取CPU的排程權後會繼續執行,再貼一遍這個圖:
1.wait方法的使用
既然是釋放當前執行緒的鎖,那麼不須有鎖才行,而且必須用該鎖的物件呼叫wait方法
比如上面是用sdf物件加鎖的,必須使用sdf.wait();
,否則會丟擲InterruptedException
private static class Car implements Runnable {
@Override
public void run() {
synchronized (sdf) {
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
try {
Thread.sleep(3000);//模擬執行3s的任務之後
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車緊急剎車....");
sdf.wait();
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動....");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
}
}
}
private static class Ambulance implements Runnable {
@Override
public void run() {
synchronized (sdf) {
System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車開始啟動,在路上跑");
System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車跑到終點");
}
}
}
複製程式碼
2.結果分析
在Car執行緒呼叫
sdf.wait();
後,鎖將被釋放,然後Ambulance執行緒就可以持有鎖執行了
如果不喚醒執行緒,執行緒將一直阻塞,就是根本停不下來
。打個比方就是sdf是司機
wait()
之後就把車鑰匙扔了,然後熄火了。鑰匙拿不回來,車就跑不了。需要notify()
獲取車鑰匙
30:48:小汽車開始啟動,在路上跑
30:51:小汽車緊急剎車....
30:51:救護車開始啟動,在路上跑
30:51:救護車跑到終點
然後就阻塞在這裡停不下來了....
複製程式碼
3.喚醒等待執行緒
注意wait和notify需要使用同一個物件,否則然並卵
在Ambulance執行緒跑完後喚醒Car執行緒,然後Car獲取所後會進入就緒態
private static class Ambulance implements Runnable {
@Override
public void run() {
synchronized (sdf) {
System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車開始啟動,在路上跑");
System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車跑到終點");
sdf.notify();
}
}
}
複製程式碼
4.結果分析
40:23:小汽車開始啟動,在路上跑
40:26:小汽車緊急剎車....
40:26:救護車開始啟動,在路上跑
40:26:救護車跑到終點
40:26:救護車:喂,哥們,醒醒,你可以開了...
40:26:小汽車開始啟動....
40:31:小汽車跑到終點
複製程式碼
四、Thread#join()
方法 和Thread#yield
1.普通程式碼
public class Main6 {
static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
public static void main(String[] args) {
Thread car = new Thread(new Car());
car.start();
System.out.println(sdf.format(System.currentTimeMillis()) + ":main執行緒結束");
}
private static class Car implements Runnable {
@Override
public void run() {
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
try {
Thread.sleep(3000);//模擬執行3s的任務之後
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
}
}
}
複製程式碼
2.結果分析
26:28:小汽車開始啟動,在路上跑
26:28:main執行緒結束
26:31:小汽車跑到終點
複製程式碼
3.join方法的使用
阻塞當前執行緒,知道join的執行緒結束或超時
public class Main6 {
static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
public static void main(String[] args) {
Thread car = new Thread(new Car());
car.start();
try {
car.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sdf.format(System.currentTimeMillis()) + ":main執行緒結束");
}
private static class Car implements Runnable {
@Override
public void run() {
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
try {
Thread.sleep(3000);//模擬執行3s的任務之後
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
}
}
}
---->[列印結果]-----------------------------
31:53:小汽車開始啟動,在路上跑
31:56:小汽車跑到終點
31:56:main執行緒結束
複製程式碼
4.yield方法測試
讓出執行緒的cpu排程權,之後再一起搶奪。
執行狀態-->就緒狀態
public class Main7 {
public static void main(String[] args) {
Thread car = new Car("car");
Thread ambulance = new Car("Ambulance");
car.start();
ambulance.start();
}
private static class Car extends Thread {
public Car(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "----" + i);
if (i == 5) {
yield();
}
}
}
}
}
複製程式碼
執行了幾組資料,大部分滿足讓出排程權後,另一個執行緒執行,也有少量情況不是。
所以yield說明我現在不急,可以劃劃水,執行權可以讓出去。不過接下來誰執行還是不定的。
五、小結
1.需要補充的點:
1.關於synchronized鎖這裡不展開,不瞭解的可以見:執行緒篇3:[-synchronized-]
2.關於synchronized鎖物件需要一致,否則鎖不住,然並卵。常用class物件,如Car.class
3.可以設定超時喚醒xxx.wait(3000)
,即3s後喚醒執行緒。
4.可以設定join超時xxx.join(3000)
,即3s後執行緒進入就緒態。
5.notifyAll:來著黑暗寒冬的隨從們,僕人們,士兵們,聽從克爾蘇加德的召喚吧!
2.簡單比較
item | 從屬 | 是否釋放鎖 | 狀態轉變為 | 異常 |
---|---|---|---|---|
sleep | Thread 靜態方法 | NO | 阻塞 | InterruptedException |
wait | Object 公共方法 | YES | 等待佇列 | IllegalMonitorStateException+InterruptedException |
notify/notifyAll | Object 公共方法 | -- | 同步佇列 | IllegalMonitorStateException |
join | Thread 公共方法 | NO | 阻塞 | InterruptedException |
yield | Thread 公共方法 | NO | 可執行態 | 無 |