1 park與unpark的使用以及原理
1-1 基本使用
- park/unpark並非線程類的方法,是concurrent的方法
// 暫停當前線程
LockSupport.park();
// 恢復某個線程的運行
LockSupport.unpark(暫停線程對象)
實例:
package chapter4;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.LockSupport;
import chapter2.Sleeper;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.test3")
public class test3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("start...");
try {
Thread.sleep(1000); // 睡眠時間1
} catch (InterruptedException e) {
e.printStackTrace();
}
log.warn("park...");
LockSupport.park();
log.warn("resume...");
},"t1");
t1.start();
Thread.sleep(2000);
log.warn("unpark..."); //睡眠時間2
LockSupport.unpark(t1);
}
}
運行結果1(設置睡眠時間1為1000ms,睡眠時間2為2000ms,即unpark在park之后執行)
[t1] WARN c.test3 - park...
[main] WARN c.test3 - unpark...
[t1] WARN c.test3 - resume...
運行結果2(設置睡眠時間1為2000ms,睡眠時間2為1000ms,即park在unpark之后執行)
[main] WARN c.test3 - unpark...
[t1] WARN c.test3 - park...
[t1] WARN c.test3 - resume...
總結:上面的2個結果說明,park與unpark成對使用時,對使用前后的次序並不敏感。原因見原理部分。
1-2 park/unpark與wait/notify的區別
- wait/notify必須在有鎖的情況下使用(需要關聯Monitor對象),park/unpark沒有這個限制條件。
- park/unpark配對使用能夠精確的指定具體的線程的阻塞/運行,notify只能隨機喚醒一個線程
- park/unpark配對使用可以先unpark,wait/notify配合使用不能夠先notify。
1-3 park/unpark的底層原理
1-3-1 先park后unpark的場景分析
step1: 線程0在執行的過程中調用park方法。
step2:檢查_counter是否為0
- 為0,獲得_mutex互斥鎖
- 為1,說明之前其他線程調用過park方法,則將_counter設為1后線程繼續執行。(先unpark后park的場景)
step3:獲得互斥鎖之后,線程進入 _cond 條件變量阻塞
step4: 某線程在執行的過程中調用unpark方法后,設置_counter為1。
step5:喚醒 _cond 條件變量中的 Thread_0
step6:Thread_0 恢復運行 ,並恢復_counter為0。
1-3-2 先unpark后park的場景分析
step1: 某線程調用unpark方法后,_counter被設置為1。
step2:線程0執行過程中調用park方法,檢查_counter是1,無法獲得互斥變量__mutex進入阻塞隊列
step3:線程0恢復_counter為0並繼續執行。
總結
從2個例子中可以看到,park方法調用后,必須滿足_counter為0,才能進入阻塞隊列。如果在park之間調用unpark,那么park方法就會失效,無法讓線程停止運行。
2 JAVA中API層面的線程10種狀態轉換
2-1 六種狀態回顧
NEW:線程剛剛被創建時,還沒有start()的狀態
RUNABLE: Java中的RUNABLE包含了操作系統層面的運行,阻塞,可運行狀態。
- 操作系統層面的線程的運行,阻塞等在Java層面無法體現出來。
BLOCKED,WAITING,TIMED_WAITINGJava API層面的阻塞
- TIMED_WAITING:使用sleep方法可能會出現
- WAITING: 使用join方法后可能會出現
- BLOCKED:使用synchronize方法可能會出現
2-2 狀態的轉換10種情況分析
假設有線程t。
情況1 NEW --> RUNNABLE
- 線程t被定義后,其狀態為NEW,當調用 t.start() 方法時,由 NEW --> RUNNABLE
情況2,3,4 RUNNABLE <--> WAITING
wait/notify|wait/interrupt
- 線程t何時從RUNNABLE變為WAITING?
- t在執行過程中通過synchroized(obj)獲取到鎖(擁有了monitor的owner),然后調用obj.wait()方法,則線程進入WAITING狀態(進入到monitor的waitset等待)。
- 線程t何時從WAITING變為RUNABLE?
- 調用 obj.notify() , obj.notifyAll() , t.interrupt() 時(notify或者使用中斷)。
- 競爭鎖成功,t 線程從 WAITING --> RUNNABLE
- 競爭鎖失敗,t 線程從 WAITING --> BLOCKED (進入到monitor的entryList等待)
- 調用 obj.notify() , obj.notifyAll() , t.interrupt() 時(notify或者使用中斷)。
join|join/interrupt(可以將“當前線程”作為“主線程”)
- 當前線程何時從RUNNABLE變為WAITING?
- 當前線程(比如主線程)需要等待t線程運行結束,調用join()的時候(此時調用線程會進入到線程t關聯的monitor中的waitset進行等待)。
- 當前線程何時從WAITING變為RUNABLE?
- t 線程運行結束 (猜測:線程t運行結束,其作為鎖對象關聯的monitor被回收,在monitor上等待的當前線程不會再等待又繼續執行)
- t線程調用當前線程的interrupt方法
park/unpark| park/interrupt(可以將當前線程作為主線程)
-
當前線程何時從RUNNABLE變為WAITING?
- 當前線程調用 LockSupport.park() 方法
-
當前線程何時從WAITING變為RUNABLE?‘
- t線程調用了LockSupport.unpark(目標線程)
- t線程調用了當前線程的interrupt方法
情況5,6,7,8 RUNNABLE <--> TIMED_WAITING(結合情況2,3,4看)
wait(n)/notify|wait(n)調用/等待足夠時間|wait(n)/interrupt
join(n)/interrupt|join(n)調用后/等待足夠時間
sleep(n)/等待足夠時間
parkNanos(nanos),parkUntil(millis) /unpark(目標線程) 或者interrupt或者等待超時
情況9 RUNNABLE <--> BLOCKED
-
t線程何時從 RUNNABLE變為BLOCKED?
- t 線程用 synchronized(obj) 嘗試獲取對象鎖時但是競爭失敗 (此時線程t會進入對象頭的阻塞隊列)
-
當前線程何時從BLOCKED變為RUNNABLE?‘
- 持 obj 鎖線程的同步代碼塊執行完畢,會喚醒該對象上所有 BLOCKED 的線程重新競爭,其中 t 線程競爭成功。
情況 10 RUNNABLE <--> TERMINATED
當前線程所有代碼運行完畢,進入 TERMINATED
2-3 WAITING與BLOCKED的區別(從圖中分辨)
總結:從上面的圖可以看出WAITING線程與BLOCKED線程在不同地方。WAITING線程在對象頭的WaitSet,而BLOCKED線程在對象頭的EntryList。
-
waiting:主動為之,wait()方法釋放cpu執行權和釋放鎖進入對象頭的Waitset ,需要notify()喚醒,然后進入到EntryList等待競爭鎖。
-
blocked:被動的,線程在競爭鎖的時候失敗,被阻塞,進入EntryList。
參考資料
20210303