sleep、yield、join方法簡介與用法 sleep與wait區別 多線程中篇(十五)

Object中的wait、notify、notifyAll,能夠用於線程間的通訊,核心原理爲藉助於監視器的入口集與等待集邏輯
經過這三個方法完成線程在指定鎖(監視器)上的等待與喚醒,這三個方法是以鎖(監視器)爲中心的通訊方法 
除了他們以外,還有用於線程調度、控制的方法,他們是sleep、yield、join方法,他們能夠用於線程的協做,他們是圍繞着線程的調度而來的 

sleep方法

有兩個版本的sleep方法,看得出來,核心仍舊是native方法
非native方法只是進行了參數校驗,接着仍舊是調用的native方法,這個情形與wait是相似的
image_5c762c46_fe1
接下來仔細看下,native版本的sleep
在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操做受到系統計時器和調度程序精度和準確性的影響。該線程不丟失任何監視器的所屬權。
注意:
sleep不會釋放鎖,不會釋放鎖,不會釋放鎖
能夠理解爲他進入監視器這個房間以後,在這房間裏面睡着了
與wait相似的是,sleep也是可中斷方法(從方法簽名能夠看得出來,可能拋出InterruptedException),也就是說若是一個線程正在sleep,若是另外的線程將他中斷(調用interrupt方法),將會拋出異常,而且中斷狀態將會擦除
因此對於sleep方法,要麼本身醒來,要麼被中斷後也會醒來
對於sleep始終有一個超時時間的設置,因此,儘管他是在監視器內睡着了,可是並不會致使死鎖,由於他終究是要醒來的
 
以下,線程休眠500毫秒,主線程50毫秒打印一次狀態
ps:sleep方法的調用結果爲狀態:TIMED_WAITING
image_5c762c46_7dda
藉助於sleep方法,能夠模擬線程的順序執行
好比下面示例,兩個階段,第二個階段將在第一個階段執行以後纔會執行
package test1;
import java.lang.Thread.State;
public class T16 {
public static void main(String[] args) {
//模擬執行任務的第一個階段的執行
Thread stepOne = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+" : 第一階段任務開始執行");
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" : 第一階段任務執行結束");
} catch (InterruptedException e) {
}
}, "firstStage");
stepOne.start();
//模擬任務第二個階段的執行
Thread stepTwo = new Thread(() -> {
while (!State.TERMINATED.equals(stepOne.getState())) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" : 我在等待第一階段任務執行結束");
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName()+" : 第二階段任務執行結束");
}, "secondStage");
stepTwo.start();
}
}
image_5c762c46_7524
另外,你應該已經注意到sleep方法都有static修飾,既然是靜態方法,在Thread中的慣例就是針對於:當前線程,當前線程,當前線程

yield方法

對於sleep或者wait方法,他們都將進入特定的狀態,伴隨着狀態的切換,也就意味着等待某些條件的發生,纔可以繼續,好比條件知足,或者到時間等
可是yield方法不涉及這些事情,他針對的是時間片的劃分與調度,因此對開發者來講只是臨時讓一下,讓一下他又不會死,就只是再等等
yield方法將會暫停當前正在執行的線程對象,並執行其餘線程,他始終都是RUNNABLE狀態
image_5c762c46_5553
不過要注意,能夠認爲yield只是一種建議性的,若是調用了yield方法,對CPU時間片的分配進行了「禮讓」,他仍舊有可能繼續得到時間片,而且繼續執行
因此一次調用yield 並不必定會表明確定會發生什麼
image_5c762c47_599f
藉助於while循環以及yield方法,能夠看得出來,也能必定程度上達到線程排序等待的效果
image_5c762c47_6985
yield也是靜態方法,因此,也是針對於當前線程,當前線程,當前線程。

join方法

三個版本的join方法
image_5c762c47_265e
方法的實現過程,與wait也是很是相似,下面兩個版本的方法一個調用join(0),一個參數校驗後,調用join(millis),因此根本仍是單參數版本的join方法
image_5c762c47_3822
在方法深刻介紹前先看個例子
一個線程,循環5次,每次sleep 1s,主線程中打印信息
從結果能夠看到,主線程老是在線程執行以後,纔會執行,也就是主線程在等待咱們建立的這個線程結束,結束了以後纔會繼續進行
image_5c762c47_4018
image_5c762c47_6de6
若是調整下順序--->start 與 join的前後順序,再次看下狀況,能夠發現順序沒有保障了
image_5c762c47_186f
結論:
主線程main中調用啓動線程(調用start),而後調用該線程的join方法,能夠達到主線程等待工做線程運行結束才執行的效果,而且join要在start調用後
如何作到的?
image_5c762c47_56b7
從上面源代碼能夠看得出來,內部調用了wait方法,因此也能明白爲啥join也會拋出InterruptedException了吧
主線程main中調用thread.join()方法,join方法至關於join(0),也就是
            while (isAlive()) {
                wait(0);
            }
而這個wait(0)就至關因而this.wait(0),this就是咱們本身建立的那個線程thread,看看方法的簽名是否是有一個synchronized
isAlive()也是this.isAlive(),也就是若是當前線程alive(已經啓動,可是未終止),那麼將持續等待,等待的臨界資源就是咱們建立的這個線程對象自己
因此這兩行代碼的含義就是:
該線程是否還存活?若是存活,調用join的那個線程將會在這個對象上進行等待(進入該線程對象的等待集)
也就是說調用一個線程的join方法,就是在這個線程是等待,這個線程對象就是咱們的鎖對象(不要疑惑,Object均可以做爲鎖,Thread實例對象怎麼不能夠?)
確定你們很奇怪,既然是等待,wait又不會本身醒來,那不是出問題了嗎?
其實線程結束後,會調用this.notifyAll,因此主線程main會被喚醒
 
若是傳遞的參數不爲0,將會走到下面的分支,會wait指定時長,與上面的邏輯一致,只不過是有指定超時時長而已
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
 
手動版本的等待結束
只是將join方法換成了同步代碼塊,鎖對象爲那個線程的實例對象thread,調用他的wait方法
從結果上看,效果同樣
(不過此處沒有持續監測isAlive(),因此一旦主線程醒來,即便線程沒有結束,也會繼續,不能百分百確保main確定等待線程結束)
image_5c762c47_69bb
不過要注意:註釋中有說明,本身不要使用Thread類的實例對象做爲鎖對象,若是是如今這種場景,使用join便可
爲何?從咱們目前來看,join方法就是以這個對象爲鎖,若是你本身在使用,又是wait又是notify(notifyAll)的,萬一出現什麼隱匿的問題咋辦?
image_5c762c47_75cb
因此join方法的原理就是:將指定的Thread實例對象做爲鎖對象,在其上進行同步,只要那個線程還活着,那麼就會持續等待(或者有限時長)
線程終止以後會調用自身this.notifyAll,以通知在其上等待的線程
簡單說,只要他活着你們就都等着, 他死了會通知,因此效果就是在哪裏調用了誰的join,哪裏就要等待這個線程結束,才能繼續
爲何要在start以後?
image_5c762c47_3634
如上面所示,將join改形成同步代碼塊以下所示,若是這段同步代碼在start方法以前
看下結果,沒有等待指定線程結束,main主線程就結束了
image_5c762c47_6b59
由於若是尚未調用start方法,那麼isAlive是false(已開始未結束),主線程根本就不會等待,因此繼續執行,而後繼續到下面的start,而後主線程結束
因此,爲何join方法必定要在start以前?
就是由於這個isAlive方法的校驗,你沒有start,isAlive就是false,就不會同步等待,因此必需要先start,而後才能join
小結:
對於join方法,有兩個關鍵:
  • 調用的哪一個對象的join?
  • 在哪裏調用的?
換一個說法:
join的效果是:一個線程等待另外一個線程(直到結束或者持續一段時間)才執行,那麼誰等待誰?
在哪一個線程調用,哪一個線程就會等待;調用的哪一個Thread對象,就會等待哪一個線程結束;

狀態圖回顧

在回顧下以前狀態一文中的切換圖,又瞭解了這幾個方法後,應該對狀態切換有了更全面的認識
image_5c762c47_3c40

總結

對於yield方法,比較容易理解,只是簡單地對於CPU時間片的「禮讓」,除非循環yield,不然一次yield,可能下次該線程仍舊可能會搶佔到CPU時間片,可能方法調用和不調用沒差異
sleep是靜態方法,針對當前線程,進入休眠狀態,兩個版本的sleep方法始終有時間參數,因此必然會在指定的時間內甦醒,他也不會釋放鎖,固然,sleep方法的調用非必須在同步方法(同步代碼塊)內
join是實例方法,表示等待誰,是用於線程順序的調度方法,能夠作到一個線程等待另一個線程,join有三個版本,指定超時時間或者持續等待直到目標線程執行結束,join也無需在同步方法(同步代碼塊)內
sleep和join都是可中斷方法,被其餘線程中斷時,都會拋出InterruptedException異常,而且會醒來
 
join方法底層依賴wait,咱們對比下wait與sleep 
  • wait和sleep都會使線程進入阻塞狀態,都是可中斷方法,被中斷後都會拋出異常
  • wait是Object的方法,sleep是Thread的方法
  • wait必須在同步中執行,sleep不須要(join底層依賴wait,可是不須要在同步中,由於join方法就是synchronized的)
  • wait會釋放鎖,sleep不會釋放鎖
  • wait(無超時設置的版本)會持續阻塞,必須等待喚醒,而sleep必然有超時,因此必定會本身醒來
  • wait 實例方法(Object),在對象上調用,表示在其上等待;sleep靜態方法,當前線程  
相關文章
相關標籤/搜索