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