- Offer來了:Java面試核心知識點精講(原理篇)
- 王磊
- 3034字
- 2020-04-03 12:50:12
3.5 線程的基本方法
線程相關的基本方法有wait、notify、notifyAll、sleep、join、yield等,這些方法控制線程的運行,并影響線程的狀態變化。
3.5.1 線程等待:wait方法
調用wait方法的線程會進入WAITING狀態,只有等到其他線程的通知或被中斷后才會返回。需要注意的是,在調用wait方法后會釋放對象的鎖,因此wait方法一般被用于同步方法或同步代碼塊中。
3.5.2 線程睡眠:sleep方法
調用sleep方法會導致當前線程休眠。與wait方法不同的是,sleep方法不會釋放當前占有的鎖,會導致線程進入TIMED-WATING狀態,而wait方法會導致當前線程進入WATING狀態。
3.5.3 線程讓步:yield方法
調用yield方法會使當前線程讓出(釋放)CPU執行時間片,與其他線程一起重新競爭CPU時間片。在一般情況下,優先級高的線程更有可能競爭到CPU時間片,但這不是絕對的,有的操作系統對線程的優先級并不敏感。
3.5.4 線程中斷:interrupt方法
interrupt方法用于向線程發行一個終止通知信號,會影響該線程內部的一個中斷標識位,這個線程本身并不會因為調用了interrupt方法而改變狀態(阻塞、終止等)。狀態的具體變化需要等待接收到中斷標識的程序的最終處理結果來判定。對interrupt方法的理解需要注意以下4個核心點。
◎ 調用interrupt方法并不會中斷一個正在運行的線程,也就是說處于Running狀態的線程并不會因為被中斷而終止,僅僅改變了內部維護的中斷標識位而已。具體的JDK源碼如下:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
◎ 若因為調用sleep方法而使線程處于TIMED-WATING狀態,則這時調用interrupt方法會拋出InterruptedException,使線程提前結束TIMED-WATING狀態。
◎ 許多聲明拋出InterruptedException的方法如Thread.sleep(long mills),在拋出異常前都會清除中斷標識位,所以在拋出異常后調用isInterrupted方法將會返回false。
◎ 中斷狀態是線程固有的一個標識位,可以通過此標識位安全終止線程。比如,在想終止一個線程時,可以先調用該線程的interrupt方法,然后在線程的run方法中根據該線程isInterrupted方法的返回狀態值安全終止線程。
public class SafeInterruptThread extends Thread {
@Override
public void run() {
if (! Thread.currentThread().isInterrupted()) {
try {
//1:這里處理正常的線程業務邏輯
sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); //重新設置中斷標識
}
}
if (Thread.currentThread().isInterrupted()){
//2:處理線程結束前必要的一些資源釋放和清理工作,比如釋放鎖、
//存儲數據到持久化層、發出異常通知等,用于實現線程的安全退出
sleep(10);
}
}
}
//3:定義一個可安全退出的線程
SafeInterruptThread thread = new SafeInterruptThread();
//4:安全退出線程
thread.interrupt();
3.5.5 線程加入:join方法
join方法用于等待其他線程終止,如果在當前線程中調用一個線程的join方法,則當前線程轉為阻塞狀態,等到另一個線程結束,當前線程再由阻塞狀態轉為就緒狀態,等待獲取CPU的使用權。在很多情況下,主線程生成并啟動了子線程,需要等到子線程返回結果并收集和處理再退出,這時就要用到join方法,具體的使用方法如下:
System.out.println("子線程運行開始!");
ChildThread childThread = new ChildThread();
childThread.join(); //等待子線程childThread執行結束
System.out.println("子線join()結束,開始運行主線程");
3.5.6 線程喚醒:notify方法
Object類有個notify方法,用于喚醒在此對象監視器上等待的一個線程,如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程,選擇是任意的。
我們通常調用其中一個對象的wait方法在對象的監視器上等待,直到當前線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程,被喚醒的線程將以常規方式與在該對象上主動同步的其他線程競爭。類似的方法還有notifyAll,用于喚醒在監視器上等待的所有線程。
3.5.7 后臺守護線程:setDaemon方法
setDaemon方法用于定義一個守護線程,也叫作“服務線程”,該線程是后臺線程,有一個特性,即為用戶線程提供公共服務,在沒有用戶線程可服務時會自動離開。
守護線程的優先級較低,用于為系統中的其他對象和線程提供服務。將一個用戶線程設置為守護線程的方法是在線程對象創建之前用線程對象的setDaemon(true)來設置。
在后臺守護線程中定義的線程也是后臺守護線程。后臺守護線程是JVM級別的,比如垃圾回收線程就是一個經典的守護線程,在我們的程序中不再有任何線程運行時,程序就不會再產生垃圾,垃圾回收器也就無事可做,所以在回收JVM上僅剩的線程時,垃圾回收線程會自動離開。它始終在低級別的狀態下運行,用于實時監控和管理系統中的可回收資源。
守護線程是運行在后臺的一種特殊線程,獨立于控制終端并且周期性地執行某種任務或等待處理某些已發生的事件。也就是說,守護線程不依賴于終端,但是依賴于JVM,與JVM“同生共死”。在JVM中的所有線程都是守護線程時,JVM就可以退出了,如果還有一個或一個以上的非守護線程,則JVM不會退出。
至此,對影響線程的核心方法基本介紹完畢,各方法對線程狀態的影響如圖3-5所示。
圖3-5
3.5.8 sleep方法與wait方法的區別
sleep方法與wait方法的區別如下。
◎ sleep方法屬于Thread類,wait方法則屬于Object類。
◎ sleep方法暫停執行指定的時間,讓出CPU給其他線程,但其監控狀態依然保持,在指定的時間過后又會自動恢復運行狀態。
◎ 在調用sleep方法的過程中,線程不會釋放對象鎖。
◎ 在調用wait方法時,線程會放棄對象鎖,進入等待此對象的等待鎖池,只有針對此對象調用notify方法后,該線程才能進入對象鎖池準備獲取對象鎖,并進入運行狀態。
3.5.9 start方法與run方法的區別
start方法與run方法的區別如下。
◎ start方法用于啟動線程,真正實現了多線程運行。在調用了線程的start方法后,線程會在后臺執行,無須等待run方法體的代碼執行完畢,就可以繼續執行下面的代碼。
◎ 在通過調用Thread類的start方法啟動一個線程時,此線程處于就緒狀態,并沒有運行。
◎ run方法也叫作線程體,包含了要執行的線程的邏輯代碼,在調用run方法后,線程就進入運行狀態,開始運行run方法中的代碼。在run方法運行結束后,該線程終止,CPU再調度其他線程。
3.5.10 終止線程的4種方式
1.正常運行結束
指線程體執行完成,線程自動結束。
2.使用退出標志退出線程
在一般情況下,在run方法執行完畢時,線程會正常結束。然而,有些線程是后臺線程,需要長時間運行,只有在系統滿足某些特殊條件后,才能觸發關閉這些線程。這時可以使用一個變量來控制循環,比如設置一個boolean類型的標志,并通過設置這個標志為true或false來控制while循環是否退出,具體的實現代碼如下:
public class ThreadSafe extends Thread {
public volatile boolean exit = false;
public void run() {
while (! exit){
//執行業務邏輯代碼
}
}
}
以上代碼在線程中定義了一個退出標志exit, exit的默認值為false。在定義exit時使用了一個Java關鍵字volatile,這個關鍵字用于使exit線程同步安全,也就是說在同一時刻只能有一個線程修改exit的值,在exit為true時,while循環退出。
3.使用Interrupt方法終止線程
使用interrupt方法終止線程有以下兩種情況。
(1)線程處于阻塞狀態。例如,在使用了sleep、調用鎖的wait或者調用socket的receiver、accept等方法時,會使線程處于阻塞狀態。在調用線程的interrupt方法時,會拋出InterruptException異常。我們通過代碼捕獲該異常,然后通過break跳出狀態檢測循環,可以有機會結束這個線程的執行。通常很多人認為只要調用interrupt方法就會結束線程,這實際上理解有誤,一定要先捕獲InterruptedException異常再通過break跳出循環,才能正常結束run方法。具體的實現代碼如下:
public class ThreadSafe extends Thread {
public void run() {
while (! isInterrupted()){ //在非阻塞過程中通過判斷中斷標志來退出
try{
Thread.sleep(5*1000); //在阻塞過程中捕獲中斷異常來退出
}catch(InterruptedException e){
e.printStackTrace();
break; //在捕獲到異常后執行break跳出循環
}
}
}
}
(2)線程未處于阻塞狀態。此時,使用isInterrupted方法判斷線程的中斷標志來退出循環。在調用interrupt方法時,中斷標志會被設置為true,并不能立刻退出線程,而是執行線程終止前的資源釋放操作,等待資源釋放完畢后退出該線程。
4.使用stop方法終止線程:不安全
在程序中可以直接調用Thread.stop方法強行終止線程,但這是很危險的,就像突然關閉計算機的電源,而不是正常關機一樣,可能會產生不可預料的后果。
在程序使用Thread.stop方法終止線程時,該線程的子線程會拋出ThreadDeatherror錯誤,并且釋放子線程持有的所有鎖。加鎖的代碼塊一般被用于保護數據的一致性,如果在調用Thread.stop方法后導致該線程所持有的所有鎖突然釋放而使鎖資源不可控制,被保護的數據就可能出現不一致的情況,其他線程在使用這些被破壞的數據時,有可能使程序運行錯誤。因此,并不推薦采用這種方法終止線程。
- Testing with JUnit
- Python數據分析基礎
- SQL Server 2012數據庫技術及應用(微課版·第5版)
- Android Studio Essentials
- Kotlin Standard Library Cookbook
- QGIS:Becoming a GIS Power User
- 微信小程序項目開發實戰
- Mastering Drupal 8 Views
- Domain-Driven Design in PHP
- Odoo 10 Implementation Cookbook
- Cocos2d-x Game Development Blueprints
- C語言程序設計與應用(第2版)
- 區塊鏈項目開發指南
- 零基礎學Scratch 3.0編程
- Xamarin Blueprints