為什么說Volatile+Interrupt是停止線程優(yōu)雅的姿勢(shì)?
使用stop方法
調(diào)用stop方法,會(huì)讓正在運(yùn)行的線程直接中止,有可能會(huì)讓一些清理性的工作得不到完成。并且stop已經(jīng)被標(biāo)記為廢棄的方法,不建議使用。
正確的使用姿勢(shì)是使用兩階段終止的模式,即一個(gè)線程發(fā)送終止指令,另一個(gè)線程接收指令,并且決定自己在何時(shí)停止。
使用標(biāo)志位
- public class RunTask {
- private volatile boolean stopFlag;
- private Thread taskThread;
- public void start() {
- taskThread = new Thread(() -> {
- while (!stopFlag) {
- System.out.println("doSomething");
- }
- });
- taskThread.start();
- }
- public void stop() {
- stopFlag = true;
- }
- }
「stopFlag上加volatile是保證可見性。我這個(gè)例子用了while循環(huán)不斷判斷,如果項(xiàng)目中用不到while的話,可以在關(guān)鍵節(jié)點(diǎn)判斷,然后退出run方法即可」
使用interrupt方法
假如我們的任務(wù)中有阻塞的邏輯,如調(diào)用了Thread.sleep方法,如何讓線程停止呢?
從線程狀態(tài)轉(zhuǎn)換圖中尋找答案
從圖中可以看到如果想讓線程進(jìn)入終止?fàn)顟B(tài)的前提是這個(gè)線程處于運(yùn)行狀態(tài)。當(dāng)我們想要終止一個(gè)線程的時(shí)候,如果此時(shí)線程處于阻塞狀態(tài),我們?nèi)绾伟阉D(zhuǎn)換到運(yùn)行狀態(tài)呢?
我們可以通過調(diào)用Thread#interrupt方法,將阻塞狀態(tài)的線程轉(zhuǎn)換到就緒狀態(tài),進(jìn)入由操作系統(tǒng)調(diào)度成運(yùn)行狀態(tài),即可終止。
那線程在運(yùn)行狀態(tài)中調(diào)用interrupt方法,會(huì)發(fā)生什么呢?
- public class RunTaskCase1 {
- private Thread taskThread;
- public void start() {
- taskThread = new Thread(() -> {
- while (true) {
- System.out.println("doSomething");
- }
- });
- taskThread.start();
- }
- public void stop() {
- taskThread.interrupt();
- }
- }
依次調(diào)用start方法和stop方法,發(fā)現(xiàn)線程并沒有停止。
「其實(shí)當(dāng)線程處于運(yùn)行狀態(tài)時(shí),interrupt方法只是在當(dāng)前線程打了一個(gè)停止的標(biāo)記,停止的邏輯需要我們自己去實(shí)現(xiàn)」
「Thread類提供了如下2個(gè)方法來判斷線程是否是中斷狀態(tài)」
- isInterrupted
- interrupted
這2個(gè)方法雖然都能判斷狀態(tài),但是有細(xì)微的差別
- @Test
- public void testInterrupt() throws InterruptedException {
- Thread thread = new Thread(() -> {
- while (true) {}
- });
- thread.start();
- TimeUnit.MICROSECONDS.sleep(100);
- thread.interrupt();
- // true
- System.out.println(thread.isInterrupted());
- // true
- System.out.println(thread.isInterrupted());
- // true
- System.out.println(thread.isInterrupted());
- }
- @Test
- public void testInterrupt2() {
- Thread.currentThread().interrupt();
- // true
- System.out.println(Thread.interrupted());
- // false
- System.out.println(Thread.interrupted());
- // false
- System.out.println(Thread.interrupted());
- }
「isInterrupted和interrupted的方法區(qū)別如下」
Thread#isInterrupted:測(cè)試線程是否是中斷狀態(tài),執(zhí)行后不更改狀態(tài)標(biāo)志 Thread#interrupted:測(cè)試線程是否是中斷狀態(tài),執(zhí)行后將中斷標(biāo)志更改為false
「所以此時(shí)我們不需要自已定義狀態(tài),直接用中斷標(biāo)志即可,之前的代碼可以改為如下」
- public class RunTaskCase2 {
- private Thread taskThread;
- public void start() {
- taskThread = new Thread(() -> {
- while (!Thread.currentThread().isInterrupted()) {
- System.out.println("doSomething");
- }
- });
- taskThread.start();
- }
- public void stop() {
- taskThread.interrupt();
- }
- }
當(dāng)線程處于阻塞狀態(tài)時(shí),調(diào)用interrupt方法,會(huì)拋出InterruptedException,也能終止線程的執(zhí)行
「注意:發(fā)生異常時(shí)線程的中斷標(biāo)志為會(huì)由true更改為false?!?/strong>
所以我們有如下實(shí)現(xiàn) 當(dāng)線程處于運(yùn)行狀態(tài):用自己定義的標(biāo)志位來退出 當(dāng)線程處于阻塞狀態(tài):用拋異常的方式來退出
- public class RunTaskCase3 {
- private volatile boolean stopFlag;
- private Thread taskThread;
- public void start() {
- taskThread = new Thread(() -> {
- while (stopFlag) {
- try {
- System.out.println("doSomething");
- TimeUnit.MICROSECONDS.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- taskThread.start();
- }
- public void stop() {
- stopFlag = true;
- taskThread.interrupt();
- }
- }
當(dāng)然也可以一直用中斷標(biāo)志來退出,「注意,當(dāng)發(fā)生異常的時(shí)候需要重置中斷標(biāo)志位」。
- public class RunTaskCase4 {
- private Thread taskThread;
- public void start() {
- taskThread = new Thread(() -> {
- while (!Thread.currentThread().isInterrupted()) {
- try {
- System.out.println("doSomething");
- TimeUnit.MICROSECONDS.sleep(100);
- } catch (InterruptedException e) {
- // 重置中斷標(biāo)志位為true
- Thread.currentThread().interrupt();
- e.printStackTrace();
- }
- }
- });
- taskThread.start();
- }
- public void stop() {
- taskThread.interrupt();
- }
- }
最后問大家一個(gè)問題?RunTaskCase3和RunTaskCase4哪種實(shí)現(xiàn)方式比較好呢?
「雖然RunTaskCase4代碼看起來更簡(jiǎn)潔,但是RunTaskCase4不建議使用,因?yàn)槿绻趓un方法中調(diào)用了第三方類庫,發(fā)生了InterruptedException異常,但是沒有重置中斷標(biāo)志位,會(huì)導(dǎo)致線程一直運(yùn)行下去,同理RunTaskCase2也不建議使用」。
本文轉(zhuǎn)載自微信公眾號(hào)「 Java識(shí)堂」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 Java識(shí)堂公眾號(hào)。