Java中Thread類詳解及常用的方法
一、Thread 的常見構(gòu)造方法
方法 | 說(shuō)明 |
---|---|
Thread() | 創(chuàng)建線程對(duì)象 |
Thread(Runnable target) | 使用 Runnable 對(duì)象創(chuàng)建線程對(duì)象 |
Thread(String name) | 創(chuàng)建線程對(duì)象并命名 |
Thread(Runnable target,String name) | 使用 Runnable 對(duì)象創(chuàng)建線程對(duì)象并命名 |
關(guān)于前兩種方法,在之前的線程創(chuàng)建介紹中有使用到
線程創(chuàng)建根本上來(lái)講有兩種創(chuàng)建方法:
創(chuàng)建一個(gè)繼承自 Thread 類的子類,重寫 Thread 中的 run 方法,調(diào)用 start 方法創(chuàng)建一個(gè)實(shí)現(xiàn) Runnable 接口的類,重寫 Thread 中的 run 方法。創(chuàng)建 Thread 實(shí)例,將自己寫的實(shí)現(xiàn) Runnable 接口的類的實(shí)例設(shè)置進(jìn)去,調(diào)用 start 方法
構(gòu)造方法三和四不過是在前面兩種構(gòu)造方法的基礎(chǔ)上多添加了一個(gè)給線程對(duì)象命名的參數(shù),方便程序員進(jìn)行調(diào)試。
代碼(以構(gòu)造方法四為例):
public class func7 { public static void main(String[] args) { Thread thread = new Thread(() ->{ while (true) { System.out.println("This is my Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } },"myThread"); //此處用 lambda 表達(dá)式代替 Runnable 實(shí)例,更加簡(jiǎn)潔,添加了一個(gè)參數(shù)指定thread線程的名字 thread.start(); while (true) { System.out.println("my main"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
程序員可以通過 JDK 自帶的jconsole工具來(lái)直觀查看這里創(chuàng)建的線程
步驟一:
運(yùn)行程序后,找到自己的 jdk 路徑 -> bin ->jconsole.exe
步驟二:雙擊該 exe 文件。選擇本地進(jìn)程,這里羅列著 Java 進(jìn)程??梢钥匆娢覀兊某绦?func7 ,點(diǎn)擊它。在菜單欄選擇線程一欄
步驟三:查看線程信息
左側(cè)可以選擇需要被查看的線程,可以看見主線程 main和新創(chuàng)建的線程 myThread ,如果沒有重命名操作的話新創(chuàng)建的線程名就會(huì)叫 Thread-0,Thread-1 這樣的名字,不方便查看。
右側(cè)顯示了該線程的被讀取的那一瞬間的狀態(tài),堆棧跟蹤顯示的是代碼具體執(zhí)行的位置
二、Thread 的常見屬性
屬性 | 方法 |
---|---|
ID | getId() |
名稱 | getName() |
狀態(tài) | getState() |
優(yōu)先級(jí) | getPriority() |
是否為后臺(tái)線程 | isDaemon() |
是否存活 | isAlive() |
是否被中斷 | isInterrupted() |
解釋:
- 線程的唯一標(biāo)識(shí)就是線程 Id
- 名稱在上面的案例中有所體現(xiàn),為調(diào)試提供了便利
- 狀態(tài)表示線程當(dāng)前所處的情況,上面的案例中,線程因?yàn)檎{(diào)用了 sleep 方法就進(jìn)入了阻塞狀態(tài)
- 優(yōu)先級(jí)表示該線程被調(diào)度到的難易度,高的更容易被調(diào)度到
- 判斷是否為后臺(tái)線程。如果是后臺(tái)線程,那么該后臺(tái)線程不會(huì)影響 java 進(jìn)程的結(jié)束;如果是非后臺(tái)線程,JVM 就會(huì)等到所有的非后臺(tái)線程執(zhí)行完畢,才會(huì)結(jié)束運(yùn)行,因此會(huì)影響到總進(jìn)程的結(jié)束
- 是否存活是來(lái)判斷線程是否還存在的方法。當(dāng)創(chuàng)建出 Thread 實(shí)例對(duì)象時(shí),線程未必就創(chuàng)建了,需要調(diào)用 start 方法,才是真正的創(chuàng)建了線程。當(dāng)線程中的 run 方法執(zhí)行完畢,線程結(jié)束被銷毀了,創(chuàng)建出的實(shí)例對(duì)象還沒被銷毀回收。所以說(shuō),創(chuàng)建出的實(shí)例對(duì)象和線程的生命周期是不完全相同的
在線程的狀態(tài)中,除了NEW 和 TERMINATED 以外的狀態(tài)都是活著的
代碼:
public class func8 { public static void main(String[] args) { Thread t = new Thread(()->{ for (int i = 0;i < 5;i ++) { System.out.println("新線程~"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } },"newThread"); System.out.println("新線程狀態(tài):" +t.getState()); //創(chuàng)建了對(duì)象,沒創(chuàng)建線程 t.start(); System.out.println("新線程狀態(tài):" +t.getState()); //創(chuàng)建了線程 System.out.println("新線程Id:"+t.getId()); System.out.println("新線程名稱:"+t.getName()); System.out.println("新線程是否為后臺(tái)線程:" + t.isDaemon()); System.out.println("新線程是否被中斷:" + t.isInterrupted()); System.out.println("新線程優(yōu)先級(jí):" + t.getPriority()); System.out.println("主線程名稱:"+Thread.currentThread().getName()); while (t.isAlive()) {} //當(dāng)t線程還存在時(shí),主線程就擱這兒循環(huán)著,直到線程結(jié)束 System.out.println("新線程狀態(tài):" +t.getState());//線程結(jié)束 } }
結(jié)果:
三、創(chuàng)建線程
創(chuàng)建 Thread 類的對(duì)象不意味著線程被創(chuàng)建出,start() 方法才是真正的在操作系統(tǒng)內(nèi)部創(chuàng)建一個(gè)新的線程,通過重寫 run 方法來(lái)描述需要執(zhí)行的任務(wù),從而真正實(shí)現(xiàn)了多線程運(yùn)行。
四、中斷線程
方法一:手動(dòng)設(shè)置標(biāo)志位,作為中斷線程的條件
public class func9 { private static Boolean flag = false;//手動(dòng)設(shè)置的標(biāo)志位 flag public static void main(String[] args) { Thread t = new Thread(() -> { while (!flag) { //flag 為真時(shí)停止循環(huán) System.out.println("myThread"); try { Thread.sleep(1000);//打印一次,阻塞一秒 } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start();//創(chuàng)建了線程 t try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; //等3秒后,在主線程中將 flag 的值改成 true,從而使線程t循環(huán)條件不成立 } }
方法二:使用 Thread 實(shí)例中的標(biāo)志位
public class func10 { public static void main(String[] args) { Thread t = new Thread() { @Override public void run() { //通過 isInerrupted()判斷標(biāo)志位是否為true,為true說(shuō)明線程要退出 while (!this.isInterrupted()) { System.out.println("my Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); //System.out.println("完善工作"); //break; } } } }; t.start();//創(chuàng)建新的線程 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } t.interrupt(); //t線程運(yùn)行3秒后,通過 interrupt() 方法將標(biāo)志位設(shè)置為 true } }
結(jié)果:
可以看見當(dāng)3秒后標(biāo)志位設(shè)置為 true,希望 t 線程中斷退出時(shí),結(jié)果只是報(bào)了個(gè) InterruptedException 異常。
事實(shí)上,當(dāng)調(diào)用 interrupt 方法時(shí),如果線程為就緒狀態(tài),就會(huì)直接修改線程中的標(biāo)志位;如果是阻塞狀態(tài),就會(huì)引起 InterruptedException 異常(因?yàn)檎{(diào)用了 sleep 方法,正阻塞著呢,結(jié)果被強(qiáng)行呼醒了)
但收到中斷線程這個(gè)信號(hào)后,出現(xiàn)了異常,只是單純的打印了一下異常,并沒有對(duì)出現(xiàn)的異常進(jìn)行反應(yīng),于是 t 線程就繼續(xù)循環(huán),就好像中斷線程這個(gè)信號(hào)只是提醒了一下,線程打個(gè)日志就完事,當(dāng)做沒聽見。
事實(shí)上,這樣的機(jī)制是有存在的道理的,如果調(diào)用了 interrupt 方法后,線程說(shuō)中斷就中斷,是非常不符合常理的,此時(shí)并不知道線程執(zhí)行到什么地方,收到中斷的信號(hào)后,怎么說(shuō)也要進(jìn)行收尾工作,由線程自己決定什么時(shí)候被銷毀
因此我們要在捕捉到 InterruptedException 異常后,進(jìn)行工作完善,然后通過 break 跳出循環(huán),結(jié)束線程
方法二和方法一相比更加好。因?yàn)榉椒ㄒ恢械闹袛鄻?biāo)志被修改后,即使當(dāng)時(shí)正在 sleep ,也會(huì)把當(dāng)下的 sleep 的時(shí)間過完,才會(huì)進(jìn)行下一輪判斷,才知道線程被中斷。方法二即使是在 sleep,收到中斷信號(hào)后,就會(huì)馬上被喚醒,中斷信息收到的更加的及時(shí)。
結(jié)果:
五、線程等待
通過 join() 方法來(lái)決定線程執(zhí)行順序(主要控制結(jié)束線程的順序)。
public class func11 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0;i < 3;i ++) { System.out.println("my Thread~~"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t1.start(); //t1.join(); for (int i = 0;i < 3;i ++) { System.out.println("my main!!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
結(jié)果:
在沒有調(diào)用 join 方法時(shí),main 線程和 t1 線程是并發(fā)的,結(jié)果輸出是相間的。調(diào)用 join 方法后,main 線程就會(huì)阻塞等待,要等 t1 線程執(zhí)行完畢,才會(huì)執(zhí)行 join 方法后的內(nèi)容
不帶參數(shù)的 join 方法,等待結(jié)束的條件就是 t1 線程結(jié)束,沒結(jié)束就會(huì)一直死等;該方法也可以帶參數(shù),通過參數(shù)來(lái)指定等待時(shí)間
六、獲取線程引用
在線程代碼中,需要獲取當(dāng)前線程對(duì)應(yīng)的 Thread 類的實(shí)例化對(duì)象,才能進(jìn)行更多的操作
方法一:通過繼承 Thread 類創(chuàng)建的線程,可以在重寫的 run 方法中通過 this 獲取當(dāng)前線程的實(shí)例
在上面的中斷線程中的方法二中就是通過 this.isInterrupted() 來(lái)獲取當(dāng)前實(shí)例是否被中斷的信息。
如果將創(chuàng)建線程的方式改成創(chuàng)建 Runnable 實(shí)例的方法,當(dāng)前的run 方法就不是 Thread 類的方法,this 指向的是 Runnable,就沒有辦法獲取 Thread 實(shí)例,更沒有辦法使用其中的方法
方法二:通過 Thread 類的 currentThread() 方法,哪個(gè)線程調(diào)用了該方法,就返回哪個(gè)線程的實(shí)例對(duì)象
七、線程休眠
該方法在前面經(jīng)常介紹,那就是 sleep 方法
一旦調(diào)用 sleep 方法,線程就會(huì)阻塞等待,等待的時(shí)間取決于指定的參數(shù)
操作系統(tǒng)是以線程為單位進(jìn)行調(diào)度的,每個(gè)線程都對(duì)應(yīng)著一個(gè) PCB,并通過雙向鏈表組織這些 PCB
操作系統(tǒng)調(diào)度 PCB 時(shí),就是從就緒隊(duì)列中選出一個(gè) PCB 去 CPU 上執(zhí)行,當(dāng)執(zhí)行著的線程調(diào)用了 sleep 方法,這個(gè) PCB 就會(huì)被移動(dòng)到阻塞隊(duì)列中,等到 sleep 的時(shí)間到了,就會(huì)回到就緒隊(duì)列中,準(zhǔn)備好被執(zhí)行
join 方法也會(huì)產(chǎn)生阻塞等待,就像線程等待中的例子,main 線程執(zhí)行到 join方法后,就到阻塞隊(duì)列中,等待對(duì)應(yīng)的 t1 線程執(zhí)行完畢,才會(huì)回到就緒隊(duì)列中,做好被執(zhí)行的準(zhǔn)備
八、線程狀態(tài)
從之前的 Thread 的常見屬性這里的代碼案例可以看出來(lái)線程的狀態(tài)不只是就緒和阻塞,即使是阻塞也分成好幾種阻塞類型
//線程的狀態(tài)是一個(gè)枚舉類型 Thread.State //打印 Java 線程中的所有狀態(tài) public class func13 { public static void main(String[] args) { for (Thread.State state : Thread.State.values()) { System.out.println(state); } } }
結(jié)果:
- NEW:表示 Thread 類的對(duì)象創(chuàng)建出,但是線程還沒有被創(chuàng)建,即沒有調(diào)用 start 方法
- RUNNABLE:就緒狀態(tài)
- BLOCKED:等待鎖時(shí)的狀態(tài)(期待下一篇關(guān)于線程安全的博客)
- WAITING:通過 wait 方法觸發(fā)(期待之后的博客)
- TIMED_WAITING:通過 sleep 方法產(chǎn)生
- TERMINATED:線程已經(jīng)執(zhí)行完畢,但 Thread 類的對(duì)象還存在,未被銷毀
總結(jié)
到此這篇關(guān)于Java中Thread類詳解及常用的方法的文章就介紹到這了,更多相關(guān)Java Thread類方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
自定義類加載器的父類為何是AppClassLoader說(shuō)明
這篇文章主要介紹了自定義類加載器的父類為何是AppClassLoader說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11JDK1.8中的ConcurrentHashMap使用及場(chǎng)景分析
這篇文章主要介紹了JDK1.8中的ConcurrentHashMap使用及場(chǎng)景分析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01SpringMVC中@RequestMapping注解的實(shí)現(xiàn)
RequestMapping是一個(gè)用來(lái)處理請(qǐng)求地址映射的注解,本文主要介紹了SpringMVC中@RequestMapping注解的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01SpringBoot中使用@ControllerAdvice注解詳解
這篇文章主要介紹了SpringBoot中使用@ControllerAdvice注解詳解,@ControllerAdvice,是Spring3.2提供的新注解,它是一個(gè)Controller增強(qiáng)器,可對(duì)controller中被 @RequestMapping注解的方法加一些邏輯處理,需要的朋友可以參考下2023-10-10