詳解Java 線程中斷
一、前言
大家肯定都使用過(guò) Java 線程開發(fā)(Thread / Runnable),啟動(dòng)一個(gè)線程的做法通常是:
new Thread(new Runnable( @Override public void run() { // todo sth... } )).start();
然而線程退出,大家是如何做的呢?一般做法可能不外乎以下兩種:
- 設(shè)置一個(gè)標(biāo)志位:true / false 來(lái)退出;
- 強(qiáng)制退出:thread.stop;(我相信,現(xiàn)在應(yīng)該沒(méi)人會(huì)使用這種方式了,因?yàn)镴DK也很早就廢棄了該方法)
可能還會(huì)有人提出,我可以用中斷來(lái)退出線程! 我只能說(shuō):Too Young Too Simple!中斷并不會(huì)使得線程結(jié)束而退出,中斷(interrupt)只是喚醒被阻塞的線程而已。
本篇,我們就來(lái)好好的聊聊:線程中斷,以及如何正確的使用線程中斷,和正確的線程退出。
二、為何 Thread.stop 被廢棄
This method is inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked ThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior. Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. If the target thread waits for long periods (on a condition variable, for example), the interrupt method should be used to interrupt the wait.
以上是官方 JDK 中的源碼注釋說(shuō)明,其含義如下:
**Thread.stop 方法天生就不安全。**使用該方法來(lái)停止線程,將會(huì)導(dǎo)致其它因?yàn)楸O(jiān)視器鎖『監(jiān)視器我們?cè)?synchronized 中就講過(guò),是 Java 的內(nèi)置鎖』而被鎖住的線程全部都解鎖?。ū举|(zhì)的后果是:沒(méi)有檢查的 ThreadDeath 異常會(huì)在棧中傳播,因而使得監(jiān)視器鎖解鎖)。如果任何一個(gè)被監(jiān)視器鎖給鎖住的對(duì)象處于一個(gè)不一致的狀態(tài),那么其被解鎖后將會(huì)被其它線程可見,潛在的結(jié)果是產(chǎn)生任何后果。**我們應(yīng)該使用一個(gè)變量來(lái)代替使用 stop 方法,告訴目標(biāo)線程退出『這里就是我們開頭所說(shuō)的第一種方法,設(shè)置一個(gè)標(biāo)志位』。**目標(biāo)線程應(yīng)該周期性的檢查這個(gè)變量,并根據(jù)這個(gè)變量來(lái)正確的退出 run 方法。如果目標(biāo)線程處于阻塞/休眠狀態(tài)(如:使用 wait、sleep、yield 方法后,線程讓出了 CPU 使用權(quán),進(jìn)而阻塞/休眠),此時(shí),該標(biāo)志位變量將不會(huì)起作用,那么,應(yīng)該使用 interrupt 方法來(lái)中斷目標(biāo)線程的阻塞/休眠狀態(tài),將其喚醒!
對(duì)于 ThreadDeath 對(duì)象,官方還有補(bǔ)充:
- 線程可以在幾乎任何地方拋出 ThreadDeath 異常。由于這一點(diǎn),所有的同步方法和(代碼)塊將必須被考慮得事無(wú)巨細(xì)。
- 線程在清理第一個(gè) ThreadDeath 異常的時(shí)候(在 catch 或 finally 語(yǔ)句中),可能會(huì)拋出第二個(gè)。清理工作將不得不重復(fù)直到到其成功。保障這一點(diǎn)的代碼將會(huì)很復(fù)雜。
所以,我們也別想著去 try-catch ThreadDeath Exception!
同樣,被廢棄的還有 Thread.resume 和 Thread.suspend。這倆方法有造成死鎖的危險(xiǎn):
- 使用suspend時(shí),并不會(huì)釋放鎖;
- 如果存在某種情況要先獲取該鎖,再進(jìn)行resume,那么就造成死鎖了;
取代這兩方法的正確方式是:Object.wait 和 Object.notify :
因?yàn)?Object.wait 進(jìn)入阻塞時(shí),會(huì)釋放鎖。
三、線程中斷的含義
Thread 中有三個(gè)與中斷相關(guān)的方法:
- 成員方法 interrupt():設(shè)置線程中斷標(biāo)志為 true ;
- 成員方法 isInterrupted():獲取線程的中斷狀態(tài),默認(rèn)為 false,調(diào)用 interrupt() 后,該方法返回 true;
- 靜態(tài)方法 Thread.interrupted():獲取線程的中斷狀態(tài),并且清除中斷狀態(tài)(設(shè)置為 false);
注:如果線程中斷后,連續(xù)兩次調(diào)用 Thread.interrupted(),第一次是 true & 清除狀態(tài),第二次結(jié)果是 false。
3.1、初步了解
我們先來(lái)通過(guò)一個(gè)例子來(lái)初步了解 thread.interrupt :
public class InterruptDemo implements Runnable { @Override public void run() { while (true) { System.out.println("Thread running..."); } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new InterruptDemo(), "InterruptDemo"); System.out.println("start thread"); thread.start(); Thread.sleep(50); System.out.println("interrupt thread"); thread.interrupt(); Thread.sleep(50); System.out.println("thread's status = " + thread.isInterrupted()); } }
輸出結(jié)果:
start thread Thread running... Thread running... ...... interrupt thread Thread running... Thread running... ...... thread's status = true Thread running... ......
我們可以看到,即便我們調(diào)用了 thread.interrupt 方法,線程也并沒(méi)有退出,仍舊繼續(xù)運(yùn)行。因此,這個(gè)例子證明了一點(diǎn):我們并不能通過(guò)"我們所認(rèn)為的"中斷來(lái)試圖"結(jié)束"正在運(yùn)行的線程。
3.2、中斷即喚醒阻塞/休眠的線程
同樣,我們?cè)賮?lái)看一個(gè)例子:
public class InterruptDemo implements Runnable { @Override public void run() { while (true) { System.out.println("Thread will sleep 10s ------------------------- running"); long timestamp = System.currentTimeMillis(); try { Thread.sleep(10000); } catch (InterruptedException e) { System.out.println("thread interrupted..."); } timestamp = System.currentTimeMillis() - timestamp; System.out.println("Thread run, total sleep = " + timestamp + "(ms)"); } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new InterruptDemo(), "InterruptDemo"); System.out.println("start thread"); thread.start(); Thread.sleep(3000); System.out.println("interrupt thread"); thread.interrupt(); System.out.println("main exit"); } }
輸出結(jié)果:
start thread Thread will sleep 10s ------------------------- running interrupt thread main exit thread interrupted... Thread run, total sleep = 3002(ms) Thread will sleep 10s ------------------------- running Thread run, total sleep = 10002(ms) Thread will sleep 10s ------------------------- running
我們可以看到,線程啟動(dòng)后,進(jìn)入睡眠(10s),3秒后被中斷喚醒,執(zhí)行完一個(gè) while 后再次進(jìn)入第二次睡眠(10s),然后周而復(fù)始。
3.3、一般標(biāo)志位法退出線程
public class InterruptDemo implements Runnable { private static final AtomicBoolean running = new AtomicBoolean(true); @Override public void run() { while (running.get()) { long timestamp = System.currentTimeMillis(); timestamp = System.currentTimeMillis() - timestamp; System.out.println("Thread run, total sleep = " + timestamp + "(ms)"); } System.out.println("Thread exit"); } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new InterruptDemo(), "InterruptDemo"); System.out.println("start thread"); thread.start(); Thread.sleep(100); System.out.println("interrupt thread"); thread.interrupt(); running.set(false); System.out.println("main exit"); } }
輸出結(jié)果:
start thread ....... Thread run, total sleep = 0(ms) interrupt thread Thread run, total sleep = 0(ms) Thread run, total sleep = 0(ms) Thread run, total sleep = 0(ms) main exit Thread exit
我們通過(guò)使用一個(gè) AtomicBoolean 變量來(lái)當(dāng)作標(biāo)志位,使得我們的線程能正常退出。 我們也可以判斷線程是否被中斷而選擇性的退出。
3.4、線程中斷退出
public class InterruptDemo implements Runnable { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { long timestamp = System.currentTimeMillis(); timestamp = System.currentTimeMillis() - timestamp; System.out.println("Thread run, total sleep = " + timestamp + "(ms)"); } System.out.println("Thread exit"); } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new InterruptDemo(), "InterruptDemo"); System.out.println("start thread"); thread.start(); Thread.sleep(100); System.out.println("interrupt thread"); thread.interrupt(); System.out.println("main exit"); } }
輸出結(jié)果:
start thread ....... Thread run, total sleep = 0(ms) interrupt thread Thread run, total sleep = 0(ms) Thread run, total sleep = 0(ms) Thread run, total sleep = 0(ms) main exit Thread exit
3.5、標(biāo)志位 + 線程中斷結(jié)合
public class InterruptDemo implements Runnable { private static final AtomicBoolean running = new AtomicBoolean(true); @Override public void run() { while (running.get()) { System.out.println("Thread will sleep 10s ------------------------- running"); long timestamp = System.currentTimeMillis(); try { Thread.sleep(10000); } catch (InterruptedException e) { System.out.println("Interrupted... Todo other things then exit......"); running.set(false); continue; } timestamp = System.currentTimeMillis() - timestamp; System.out.println("Thread run, total sleep = " + timestamp + "(ms)"); } System.out.println("Thread exit"); } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new InterruptDemo(), "InterruptDemo"); System.out.println("start thread"); thread.start(); Thread.sleep(3000); System.out.println("interrupt thread"); thread.interrupt(); System.out.println("main exit"); } }
輸出結(jié)果:
start thread Thread will sleep 10s ------------------------- running interrupt thread main exit Interrupted... Todo other things then exit...... Thread exit
四、總結(jié)
本文我們分析了線程的中斷,并讓大家了解了中斷的含義:只是告訴該線程,你被『中斷』了,至于你想干嘛,還是由你自己來(lái)決定。同時(shí),我們也簡(jiǎn)單分析了幾個(gè)廢棄的方法的原因。希望大家學(xué)習(xí)了本文之后,能正確且合理的設(shè)計(jì),線程如何安全的退出。
五、附錄
- Object.wait:阻塞當(dāng)前線程,釋放持有的鎖;
- Object.notify:?jiǎn)拘旬?dāng)前對(duì)象上被阻塞的線程,使其進(jìn)入就緒狀態(tài);
- Object.notifyAll:?jiǎn)拘阉芯€程;
- Thread.sleep:指定當(dāng)前線程休眠一定時(shí)間,讓出CPU,但不會(huì)釋放同步資源鎖;
- Thread.yield:讓出CPU使用權(quán),讓自己和其它線程來(lái)爭(zhēng)奪使用CPU的機(jī)會(huì),因此,使用此方法后,并不能保證該線程又再次拿到CPU而恢復(fù)運(yùn)行(使用此方法后,優(yōu)先級(jí)高的線程拿到CPU的概率較大,但優(yōu)先級(jí)低的線程也有概率拿到CPU而執(zhí)行),同理不會(huì)釋放同步資源鎖;
以上就是詳解Java 線程中斷的詳細(xì)內(nèi)容,更多關(guān)于Java 線程中斷的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
教你怎么用Java通過(guò)關(guān)鍵字修改pdf
此方法只適合通過(guò)關(guān)鍵字位置,在pdf上添加字符直接上代碼,代碼比較長(zhǎng),大部分自己的理解都在代碼注釋中了,需要的朋友可以參考下2021-05-05SpringBoot使用MockMvc進(jìn)行單元測(cè)試的實(shí)例代碼
在Spring Boot應(yīng)用程序中,使用MockMvc進(jìn)行單元測(cè)試是一種有效的方式,可以驗(yàn)證控制器的行為和API的正確性,在這篇博客中,我們將介紹如何使用MockMvc對(duì)用戶控制器進(jìn)行測(cè)試,感興趣的朋友可以參考下2024-01-01springcloud安裝rabbitmq并配置延遲隊(duì)列插件的過(guò)程詳解
本期主要講解如何利用docker快速安裝rabbitmq并且配置延遲隊(duì)列插件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05Spring?cloud負(fù)載均衡@LoadBalanced?&?LoadBalancerClient
由于Spring?cloud2020之后移除了Ribbon,直接使用Spring?Cloud?LoadBalancer作為客戶端負(fù)載均衡組件,我們討論Spring負(fù)載均衡以Spring?Cloud2020之后版本為主,學(xué)習(xí)Spring?Cloud?LoadBalance2023-11-11你知道在Java中Integer和int的這些區(qū)別嗎?
最近面試,突然被問(wèn)道,說(shuō)一下Integer和int的區(qū)別.額…可能平時(shí)就知道寫一些業(yè)務(wù)代碼,包括面試的一些Spring源碼等,對(duì)于這種特別基礎(chǔ)的反而忽略了,導(dǎo)致面試的時(shí)候突然被問(wèn)到反而不知道怎么回答了.哎,還是乖乖再看看底層基礎(chǔ),順帶記錄一下把 ,需要的朋友可以參考下2021-06-06Springboot中靜態(tài)文件的兩種引入方式總結(jié)
這篇文章主要介紹了Springboot中靜態(tài)文件的兩種引入方式總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03java 設(shè)計(jì)模式(DAO)的實(shí)例詳解
這篇文章主要介紹了java 設(shè)計(jì)模式(DAO)的實(shí)例詳解的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-09-09springboot熱部署知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家整理了關(guān)于springboot熱部署的知識(shí)點(diǎn)內(nèi)容,有興趣的朋友們參考學(xué)習(xí)下。2019-06-06