Java線程狀態(tài)及切換、關閉線程的正確姿勢分享
前言
在講線程之前有必要討論一下進程的定義:進程是程序在一個數(shù)據(jù)集合上運行的過程,它是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位。進程實體由程序段, 數(shù)據(jù)段 PCB(進程控制塊)組成。線程又是什么?線程可以看做輕量級進程,線程是進程的執(zhí)行單元,是進程調(diào)度的基本單位
本文將詳細介紹關于Java線程狀態(tài)及切換、關閉線程的相關內(nèi)容,下面話不多說了,來一起看看詳細的介紹吧
1、線程狀態(tài)及切換
Java中的線程有六種狀態(tài),使用線程Thread內(nèi)的枚舉類來實現(xiàn),如下,我對每個狀態(tài)都進行了一定的解釋。
public enum State { /** 表示一個線程還沒啟用(即未調(diào)用start方法)*/ NEW, /** * JVM中執(zhí)行的線程都是處于這個狀態(tài)的,但是處于這個狀態(tài)不一定在JVM中執(zhí)行, * 也就是說,只有這個狀態(tài)有資格被JVM調(diào)度從而獲得時間片執(zhí)行。 */ RUNNABLE, /** * 線程在等待獲取鎖資源從而進入阻塞狀態(tài), * 在這個狀態(tài)中,其一直監(jiān)視鎖的動態(tài),隨時準備搶占鎖 * 若獲得鎖資源,重新進入RUNNABLE狀態(tài) */ BLOCKED, /** * 當調(diào)用Object.wait、Thread.join或者LockSupport類的park方法的時候,線程進入此狀態(tài), * 該狀態(tài)若無其他線程主動喚醒,則無期限的等待。 * 喚醒的方法包括:Object.notify(喚醒隨機一個)、Object.notifyAll(喚醒全部線程), * 被喚醒的線程重新進入RUNNABLE狀態(tài) */ WAITING, /** * 同WAITING狀態(tài),不過不同的是調(diào)用的方法加上了時間的限制, * 例如:Object.wait(10)、Thread.sleep(10)、Thread.join(10)、LockSupport.parkNanos(10)、LockSupport.parkUntil(10)這些方法 * 喚醒的方法有兩種: * 1、時間過期。 * 2、其他線程調(diào)用了notify或者notifyAll * 喚醒之后同樣進入RUNNABLE狀態(tài) */ TIMED_WAITING, /** 線程的終點(正常死亡或者被終止)*/ TERMINATED; }
除了NEW和TERMINATED之外,其他的狀態(tài)都是可以相互轉(zhuǎn)換的,其轉(zhuǎn)換過程如下圖所示
這里特別講一下RUNNABLE狀態(tài),在這個狀態(tài)中線程并不一定在執(zhí)行程序,只有被JVM調(diào)度的線程才能獲得執(zhí)行的時間片,并且只有這個狀態(tài)的線程才能夠獲得時間片,換句話說,被JVM調(diào)度并且獲得時間片是只屬于處于RUNNABLE狀態(tài)線程的權利。為了便于理解,可以將RUNNABLE分成Runnable和Running兩個狀態(tài)(當然,你也可以換成其他的,這里我只是自己好理解),那么上面的線程轉(zhuǎn)換圖就轉(zhuǎn)變成了下面這樣(參考《Java并發(fā)編程的藝術》中的線程狀態(tài)圖):
關于線程狀態(tài)轉(zhuǎn)換的例子,可以通過下面的代碼加深理解
public class Test { public static void main(String[] args) { Test test = new Test(); // 1.NEW狀態(tài) Thread thread = new Thread(() -> { // 3.進行test對象鎖的爭奪,若搶到鎖則繼續(xù)執(zhí)行,否則進入BLOCKED狀態(tài)監(jiān)控該鎖,重新獲得后進入RUNNABLE synchronized (test) { try { // 4.進入TIMED_WAITING狀態(tài),100ms后重新進入RUNNABLE狀態(tài)爭奪時間片 Thread.sleep(100); // 5.重新獲得時間片之后,進入WAITING狀態(tài) test.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 6.正常run()方法執(zhí)行完畢后線程結束,進入TERMINATED }); // 2.調(diào)用start()方法,線程進入RUNNABLE狀態(tài) thread.start(); } } 注:代碼執(zhí)行的順序為注釋的序號
2、正確的結束一個線程
在上面的例子中我們看到線程的run方法正常執(zhí)行完畢之后線程就正常死亡進入TERMINATED狀態(tài)了,那么如果我們有中途停止線程的需求,我們應該如何正確的結束一個線程呢?
使用interrupt()方法:在線程內(nèi)部,其定義了一個變量來標識當前線程是否處于被打斷狀態(tài),調(diào)用interrupt()方法則使這個狀態(tài)變?yōu)閠rue。我們采用這個方法加異常處理的方式來結束一個線程。
public static void main(String[] args) { Thread thread = new Thread(() -> { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); // 這里的return是必須的,原因后面說明 return; } System.err.println("thread interrupt test..."); }); thread.start(); thread.interrupt(); System.out.println("main thread end..."); } // 結果圖:異常后面的語句不會打印
這里關于線程中的打斷標識變量(之后以interrupt稱)需要說明的是,在特定的情況下其狀態(tài)會被重置。
1、線程內(nèi)部在catch了異常了之后interrupt的狀態(tài)會被重置為false。
2、線程調(diào)用了Thread.interrupted()方法之后,interrupt的狀態(tài)會被重置為false。如果需要判斷線程是否中斷的話可以使用對象方法isInterrupted(),此方法不會重置。
所以在剛才的代碼中需要加入return來結束線程,否則的話線程還是會繼續(xù)往下執(zhí)行,如下圖
使用isInterrupted()實現(xiàn):
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { int k = 0; while (k++ < 10) { System.out.println("do something..." + k); } } System.err.println("thread end..."); }); thread.start(); Thread.sleep(1); // 主線程流程執(zhí)行完了,需要停止線程 thread.interrupt(); }
使用標識位來實現(xiàn):定義一個變量標識線程是否終止,若終止了則退出run方法。跟上面isInterrupted()的實現(xiàn)一樣,不過換成了volatile變量而已。
public class Test { public static volatile boolean interrupted = false; public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while (!interrupted) { int k = 0; while (k++ < 10) { if (interrupted) { System.err.println("thread invoke end...."); return; } System.out.println("do something..." + k); } } System.err.println("thread end..."); }); thread.start(); Thread.sleep(1); // 主線程流程執(zhí)行完了,需要停止線程 interrupted = true; } } // 結果圖
stop()方法——不正確的線程中斷方法
在線程提供的方法中還有一個方法可以強制關閉線程——stop()。這個方法可以說是相當?shù)陌缘溃o人一種“我不管,我就是要你現(xiàn)在立刻死亡(指線程)”的感覺,并且其還會釋放線程所有的鎖資源,這樣可能會導致出現(xiàn)數(shù)據(jù)不一致從而出現(xiàn)線程不安全的情況,如下面例子。
public class Test { public static volatile boolean flag = false; public int state = 0; public static void main(String[] args) throws InterruptedException { Test test = new Test(); Thread thread = new Thread(() -> { synchronized (test) { try { test.state = 1; Thread.sleep(100); if (flag) { test.state = 2; } System.err.println("thread execute finished..."); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); Thread.sleep(1); thread.stop(); flag = true; System.out.println("state狀態(tài):" + test.state); } } // 在這段代碼中,進入線程時默認將state賦為1,接著過一段時間后如果觸發(fā)了特定條件則把state賦為2,但是在特定條件觸發(fā)之前,線程就被終止掉了,這個特定條件雖然符合但卻沒辦法執(zhí)行,從而導致數(shù)據(jù)的不一致。 // 結果圖
所以,我們應該采用上面兩種正確的方式而不是stop()來中止線程。此外,stop()方法若在線程start()之前執(zhí)行,那么在線程啟動的時候就會立即死亡。
若有不對之處,望各位不吝指教(反正免費,對吧)。
總結
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。
相關文章
Java 添加數(shù)字簽名到excel及檢測,刪除簽名
這篇文章主要介紹了Java 添加數(shù)字簽名到excel及檢測,刪除簽名的方法,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下2021-04-04java循環(huán)遍歷無規(guī)則json的方式:gson、fastjson、orgjson
這篇文章主要介紹了java循環(huán)遍歷無規(guī)則json的方式:gson、fastjson、orgjson,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08spring boot 添加admin監(jiān)控的方法
這篇文章主要介紹了spring boot 添加admin監(jiān)控的相關知識,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-02-02SpringBoot+MyBatisPlus中樂觀鎖的實現(xiàn)示例
樂觀鎖是一種用于解決并發(fā)沖突的機制,在數(shù)據(jù)庫中用于保護數(shù)據(jù)的一致性,本文主要介紹了SpringBoot+MyBatisPlus中樂觀鎖的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2023-08-08詳解Spring Cloud Netflix Zuul中的速率限制
這篇文章主要介紹了詳解Spring Cloud Netflix Zuul中的速率限制,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11