Java線程中的常見方法(start方法和run方法)
start方法和run方法
$start()$方法用來啟動一個線程,這時此線程處於就緒(可運行)狀態,並沒有運行,一旦得到$cpu$時間片,就開始執行$run()$方法。而直接調用$run()$方法,僅僅隻是調用瞭一個類裡的方法,其本質上還是在當前線程中執行的,因此隻有使用$start()$方法來調用$run()$方法才能實現真正的多線程。
示例代碼
@Slf4j(topic = "c.Test4") public class Test4 { public static void main(String[] args) { Thread t1 = new Thread("t1"){ @Override public void run() { log.debug("running"); } }; t1.run(); } }
上述代碼是直接調用的$run()$方法。可以看到打印信息裡,是$main$線程執行瞭這個方法。
@Slf4j(topic = "c.Test4") public class Test4 { public static void main(String[] args) { Thread t1 = new Thread("t1"){ @Override public void run() { log.debug("running"); } }; t1.start(); } }
而如果使用$start()$方法啟動,才是真正的由$t1$線程執行的$run$方法。
註意
需要註意的是,當$Thread$對象調用瞭$start()$方法後,就會進入就緒狀態,處於就緒狀態時無法再調用$start()$方法,否則就會拋出$IllegalThreadStateException$異常,如下代碼所示
@Slf4j(topic = "c.Test4") public class Test4 { public static void main(String[] args) { Thread t1 = new Thread("t1"){ @Override public void run() { log.debug("running"); } }; t1.start(); t1.start(); } }
異常信息:
sleep方法與yield方法
sleep
- 調用$sleep()$方法會讓當前線程從$Running$狀態變成$Time Waiting$狀態(阻塞)
- 其它線程可以使用$interrupt$方法打斷正在睡眠的線程,此時$sleep$方法會拋出InterruptedException
- 睡眠結束後的線程未必會立刻得到執行
- 建議用$TimeUnit$的$sleep$代替$Thread$的$sleep$來獲得更好的可讀性示例代碼
@Slf4j(topic = "c.Test5") public class Test5 { public static void main(String[] args) { Thread t1 = new Thread("t1"){ @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.start(); log.debug("t1 state {}", t1.getState()); //讓主線程休眠500ms try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("t1 state {}", t1.getState()); } } //17:13:21.729 [main] DEBUG c.Test5 - t1 state RUNNABLE //17:13:22.245 [main] DEBUG c.Test5 - t1 state TIMED_WAITING
上述代碼中,首先啟動$t1$線程,此時打印線程的狀態應該是處於$RUNNABLE$狀態,而讓主線程休眠是防止主線程先執行打印,但是還未進入到$sleep()$狀態。當執行到$run()$裡邊的$sleep$方法時,線程進入$TIMED WAITING$狀態
@Slf4j(topic = "c.Test6") public class Thread6 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread("t1") { @Override public void run() { try { log.debug("enter sleep"); Thread.sleep(2000); } catch (InterruptedException e) { log.debug("wake up"); e.printStackTrace(); } } }; t1.start(); Thread.sleep(1000); log.debug("interrupt t1"); //被喚醒 t1.interrupt(); } }
執行結果
上述代碼中,當$start$方法啟動後,$t1$線程進入睡眠狀態,打印提示信息,睡眠時間為$2s$,在$main$線程中睡眠$1s$後打斷$t1$線程的睡眠,提示打斷信息,並且調用$interrupt()$方法,此時線程被打斷,拋出異常。
$TimeUnit$類中新增瞭以什麼單位去睡眠,可讀性更好,但是本質上沒區別,隻是進行瞭單位換算
TimeUnit.SECONDS.sleep(1);//該語句作用是睡眠一秒
yield
調用$yield$會讓當前進程從$Running$進入到$Runnable$就緒狀態,然後調度執行其他線程具體的實現依賴於操作系統的任務調度器,(即當任務調度器中沒有其他任務時,即使讓出$cpu$,也會繼續執行該線程)$sleep$執行後是進入阻塞狀態,此時睡眠時間不結束,就不會分配$cpu$給該線程,但是$yield$是進入就緒狀態,即如果沒有其他線程需要執行,那麼還會給該線程分配時間片,這是$sleep$和$yield$的最大區別線程優先級
線程優先級
會提示調度器優先調度該線程,但它僅僅是一個提示,調度器可以忽略他
如果$cpu$比較忙,那麼優先級高的會獲得更多的時間片,可$cpu$空閑時,優先級幾乎沒有
sleep的應用-防止cpu占用100%
在沒有利用$cpu$來計算時,不要讓$while(true)$空轉浪費$cpu$,這時可以可以使用$yield$或者$sleep$來讓$cpu$的使用權交給其他程序
while (true) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } }
可以使用$wait$或者條件變量達到類似的效果
不同的是後兩者都需要加鎖,並且需要相應的喚醒操作,一般適用於要進行同步的場景
$sleep$適用於無需鎖同步的場景
join方法
以下程序的打印結果:
@Slf4j(topic = "c.Test6") public class Test6 { static int r = 0; public static void main(String[] args) { test(); } private static void test() { log.debug("開始"); Thread t = new Thread("t1") { @Override public void run() { log.debug("開始"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("結束"); r = 10; } }; t.start(); log.debug("r的值是{}", r); log.debug("結束"); } }
因為主線程和$t1$線程是並行的,$t1$線程需要$1s$後才能計算出$r$的值,而主線程一開始就要打印出$r$的值,因此打印的值為0
解決方法:
在$t.start();$後邊加上$t.join();$即可。$join$的作用是等待某線程運行結束。
以調用方的角度來說,需要等待結果返回才能繼續執行就是同步,不需要等待返回結果就能繼續執行的就是異步。
因此$join$方法實際上是讓其同步執行
有實效的等待
$join(毫秒)$方法裡可以有一個參數是傳入等待的時間,如果線程執行時間大於等待時間,則等待時間到瞭之後,就會停止等待。如果線程執行時間小於等待時間,則線程執行完畢之後,等待也會跟著結束。不會把設置的等待時間過完。
interrupt方法
打斷$sleep, wait, join$的線程,即打斷阻塞狀態的線程
打斷$sleep$的線程,會清空打斷狀態
@Slf4j(topic = "c.Test7") public class Test7 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread("t1"){ @Override public void run() { log.debug("sleep..."); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t.start(); Thread.sleep(1000); log.debug("interrupt"); t.interrupt(); log.debug("打斷標記: {}", t.isInterrupted()); } }
打斷正常運行的線程,不會清空打斷狀態
因此我們可以在線程中判斷打斷標記,來決定是否被打斷,以及執行被打斷之前的收尾工作。
@Slf4j(topic = "c.Test8") public class Test8 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread("t1"){ @Override public void run() { while (true) { if (Thread.currentThread().isInterrupted()) { log.debug("線程被打斷瞭"); break; } } } }; t.start(); Thread.sleep(1000); log.debug("interrupt"); t.interrupt(); } }
守護線程
默認情況下,$java$需要等待所有線程都運行結束,才會結束。有一種特殊的線程叫做守護線程,隻要其他非守護線程運行結束瞭,即使守護線程的代碼沒有執行完畢,也會強制結束。
@Slf4j(topic = "c.Test10") public class Test10 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread("t1") { @Override public void run() { while (true) { } } }; //設置線程為守護線程 t.setDaemon(true); t.start(); Thread.sleep(1000); log.debug("主線程結束"); } }
如果不把$t$設置為守護線程,則因為線程內部的死循環,導致程序不會結束運行。
到此這篇關於Java線程中的常見方法(start方法和run方法)的文章就介紹到這瞭,更多相關Java線程方法內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 一篇文章掌握Java Thread的類及其常見方法
- 新手瞭解java 多線程基礎知識
- Java多線程Thread類的使用及註意事項
- Java線程中斷interrupt的常用方法
- 詳解Java停止線程的四種方法