线程的状态转换、sleep()、wait()、yeild()、终止线程的方法、线程之间的协作(join()、wait() notify() notifyAll()、await() signal() )
1.線程的狀態轉換
1.1 新建(New)
創建后尚未啟動
1.2 可運行(Runnable)
可能正在運行,也可能正在等待 CPU 時間片。
包含了操作系統線程狀態中的 Running 和 Ready。
1.3 阻塞(Blocking)
等待獲取一個排它鎖,如果其線程釋放了鎖就會結束此狀態。
1.4 無限期等待(Waiting)
等待其它線程顯式地喚醒,否則不會被分配 CPU 時間片。
| 沒有設置 Timeout 參數的 Object.wait() 方法 | Object.notify() /Object.notifyAll() |
| 沒有設置 Timeout 參數的 Thread.join() 方法 | 被調用的線程執行完畢 |
| LockSupport.park() 方法 | ? |
1.5?限期等待(Timed Waiting)
無需等待其它線程顯式地喚醒,在一定時間之后會被系統自動喚醒。
調用 Thread.sleep() 方法使線程進入限期等待狀態時,常常用“使一個線程睡眠”進行描述。
調用 Object.wait() 方法使線程進入限期等待或者無限期等待時,常常用“掛起一個線程”進行描述。
睡眠和掛起是用來描述行為,而阻塞和等待用來描述狀態。
阻塞和等待的區別在于,阻塞是被動的,它是在等待獲取一個排它鎖。而等待是主動的,通過調用 Thread.sleep() 和 Object.wait() 等方法進入。
| Thread.sleep() 方法 | 時間結束 |
| 設置了 Timeout 參數的 Object.wait()方法 | 時間結束 / Object.notify() /Object.notifyAll() |
| 設置了 Timeout 參數的 Thread.join()方法 | 時間結束 / 被調用的線程執行完畢 |
| LockSupport.parkNanos() 方法 | - |
| LockSupport.parkUntil() 方法 | - |
1.6 死亡(Terminated)
可以是線程結束任務之后自己結束,或者產生了異常而結束。
2. sleep()、wait()、yeild()
2.1 簡介
2.1.1 sleep()
sleep()方法是Thread類的靜態方法。
調用此方法是線程用來控制自身的執行流程,會休眠當前正在執行的線程,可能是在執行過程中需要有一段時間不執行任何操作。
但是如果一個線程調用了sleep對象,則會進入阻塞狀態,CPU的擁有權則會被撤去,直到sleep時間到才會進入到就緒狀態,等待下次獲得CPU執行權限再去執行。
public void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();} }2.1.2 wait()
wait()方法則是Object類的方法。
用于進程間通信。
調用wait()方法則會讓當前擁有該對象鎖的線程等待,直到其他線程調用notify()或者notifyAll()方法。
不過開發人員也可以設定一個時間,讓其自動醒來。
調用某個對象的wait()方法的線程就會釋放該對象的鎖,從而使線程所在對象中的其他synchronized方法可以被別的線程使用;
//設置超時: wait(long timeout); wait(long timeout,int nanos); //timeout 單位是ms,naos單位為ns。2.1.3 yield()
yield()方法是Thread類的靜態方法。
對靜態方法它的調用聲明了當前線程已經完成了生命周期中最重要的部分,可以切換給其它線程來執行。
該方法只是對線程調度器的一個建議,而且也只是建議具有相同優先級的其它線程可以運行。
public void run() {Thread.yield(); }2.2 對比
2.2.1 sleep()和wait()
| Thread | Object |
| 不會 | 會 |
| 參數設置 | 其它線程調用notify()或notifyAll() |
| 任何位置 | 必須放在synchronized同步的obj的臨界區中使用 |
| 可能會拋出 InterruptedException 因為異常不能跨線程傳播回 main() 中,因此必須在本地進行處理。 線程中拋出的其它異常也同樣需要在本地進行處理。 | wait()、notify()和notifyAll()無需捕獲異常。 |
2.2.2 sleep()和yield()
① sleep()方法給其他線程運行機會時不考慮線程的優先級,因此會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
?② 線程執行sleep()方法后轉入阻塞(blocked)狀態,而執行yield()方法后轉入就緒(ready)狀態;?
③ sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
④ sleep()方法比yield()方法(跟操作系統CPU調度相關)具有更好的可移植性。
表格對比:
?
| 不考慮 | 同等或更高優先級的 |
| 阻塞blocked | 就緒ready |
| InterruptedException | 未聲明任何異常 |
| 更好 | ? |
2.3 sleep(1000)方法執行后,該線程在多長時間可獲得對CPU的控制?
sleep()方法指定的是一個線程不會運行的最短時間,當睡眠結束后,線程返回到的是可運行狀態,而不是運行狀態,還需要等待CPU調度執行。
2.4 wait, notify 和 notifyAll這些方法不在thread類里面,在Object類中的原因
簡單的說,由于wait,notify和notifyAll都是鎖級別的操作,把它們定義在Object類中因為鎖屬于對象。
一個很明顯的原因是Java提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。如果線程需要等待某些鎖那么調用對象中的wait()方法就有意義了。如果wait()方法定義在Thread類中,線程正在等待的是哪個鎖就不明顯了。
這三種方法被調用的地方必須都是synchronized修飾的方法或代碼塊中。
- wait():在線程的代碼流程中如果某個synchronized方法或者代碼塊執行了某個對象的wait()方法則該對象處于wait狀態,釋放持有的對象鎖,直到該對象調用notify或者notifyAll方法才能被喚醒。正在被執行的線程就會釋放該對象的鎖,進入到阻塞狀態。
- notify():喚醒一個處于等待狀態的線程,當然在調用此方法的時候,并不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且與優先級無關;?喚醒的線程可以獲得對象鎖,然后進入到就緒狀態,等待系統調度然后去執行。
- notityAll():喚醒所有處于等待狀態的線程,該方法并不是將對象的鎖給所有線程。所有的線程被喚醒后,接著進行競爭,獲取對象鎖的線程進入到就緒態,等待系統調度去執行,執行結束后就會釋放對象鎖,之前競爭失敗處于被喚醒階段的線程就會繼續重復剛才的動作,直到所有被喚醒的線程都執行結束。
注意:在執行notify()方法之后,當前線程不會馬上釋放該對象鎖,呈wait狀態的線程也不能馬上獲取該對象鎖,要等到執行notify()方法的線程將程序執行完,也就是退出synchronized代碼塊之后,當前線程才會釋放鎖,呈wait狀態所在的線程才可以獲取該對象鎖。
3. 終止線程的方法
3.1 stop()
當調用Thread.stop()來終止線程時,它會釋放已經鎖定的所有監視資源。
如果當前任何一個受這些監視資源保護的對象處于一個不一致的狀態,其他線程將會“看”到這個不一致的狀態,可能會導致程序運行的不確定性。
3.2 suspend()
容易發生死鎖。
調用suspend()不會釋放鎖,這就導致,如果用一個suspend掛起一個有鎖的線程,那么在鎖恢復之前將不會被釋放。
如果調用了suspend(),線程將試圖取得相同的鎖,程序就會發生死鎖。
Java語言已經不建議用以上兩種方法來終止線程了。
3.3 具體方法
一般采用的方法是,讓線程自行進入Dead狀態。
- 一個線程進入Dead狀態,即執行完run()方法。
3.3.1 設置flag
通過設置一個flag標志來控制循環是否執行,通過這個方式來讓線程離開run()從而終止線程。
public class MyThread implements Runnable{private volatile boolean flag;public void stop(){falg=false;}public void run(){if(flag){//do something;}} }3.3.2 用interrupt()方法
上面的方法不適用于線程處于非運行狀態的情況(即,當調用sleep()或wait()或被I/O阻塞),此時可以使用interrupt()打破阻塞的情況。
當interrupt()被調用時,會拋出InterruptedException異常,可通過在run()中捕獲這個異常來讓線程安全退出。
public class MyThread{public static void main(String[] args){Thread thread=new Thread(new Runnable(){public viod run(){System.out.println("Thread go to sleep");try{Thread.sleep(5000);System.out.println("thread finish");}catch(InterruptedException e){System.out.println("thread is interrupted");}}});thread.start();thread.interrupt(); }}如果程序因為I/O停滯,進入非運行狀態,基本上要等到I/O完成才能離開這個狀態,在這種情況下,無法使用interrupt()方法來使程序離開run()方法。
讓程序離開run()就是使用cloae()方法來關閉流。在這種情況下會已發IOException異常,run()可以通過捕獲這個異常來安全地結束線程。
3.4 return
使用return方法停止線程。
4. 線程之間的協作
當多個線程可以一起工作去解決某個問題時,如果某些部分必須在其它部分之前完成,那么就需要對線程進行協調。
4.1 join()
在線程中調用另一個線程的 join() 方法,會將當前線程掛起,而不是忙等待,直到目標線程結束。
對于以下代碼,雖然 b 線程先啟動,但是因為在 b 線程中調用了 a 線程的 join() 方法,b 線程會等待 a 線程結束才繼續執行,因此最后能夠保證 a 線程的輸出先于 b線程的輸出。
public class JoinExample {private class A extends Thread {@Overridepublic void run() {System.out.println("A");}} private class B extends Thread {private A a;B(A a) {this.a = a;} @Overridepublic void run() {try {a.join();} catch (InterruptedException e) {e.printStackTrace();} System.out.println("B");}} public void test() {A a = new A();B b = new B(a);b.start();a.start();} }public static void main(String[] args) {JoinExample example = new JoinExample();example.test(); }運行結果:
4.2?wait() notify() notifyAll()
調用 wait() 使得線程等待某個條件滿足,線程在等待時會被掛起,當其他線程的運行使得這個條件滿足時,其它線程會調用 notify() 或者 notifyAll() 來喚醒掛起的線程。
它們都屬于 Object 的一部分,而不屬于 Thread。只能用在同步方法或者同步控制塊中使用,否則會在運行時拋出
IllegalMonitorStateExeception。
使用 wait() 掛起期間,線程會釋放鎖。
- 這是因為,如果沒有釋放鎖,那么其它線程就無法進入對象的同步方法或者同步控制塊中,
- 那么就無法執行 notify() 或者notifyAll() 來喚醒掛起的線程,造成死鎖。
運行結果:
4.3?await() signal() signalAll()
java.util.concurrent 類庫中提供了 Condition 類來實現線程之間的協調,可以在Condition上調用 await() 方法使線程等待,其它線程調用 signal() 或 signalAll() 方法喚醒等待的線程。
相比于 wait() 這種等待方式,await() 可以指定等待的條件,因此更加靈活。
使用 Lock 來獲取一個 Condition 對象。
public class AwaitSignalExample {private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();public void before() {lock.lock();try {System.out.println("before"); condition.signalAll();} finally {lock.unlock();}} public void after() {lock.lock();try {condition.await();System.out.println("after");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}} }public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();AwaitSignalExample example = new AwaitSignalExample();executorService.execute(() -> example.after());executorService.execute(() -> example.before()); }運行結果:
?
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的线程的状态转换、sleep()、wait()、yeild()、终止线程的方法、线程之间的协作(join()、wait() notify() notifyAll()、await() signal() )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Executor框架、ThreadPoo
- 下一篇: 中断(interrupted()、isI