對中斷interrupt的理解


一、中斷

線程的幾種狀態:新建、就緒、運行、阻塞、死亡。參考:線程的幾種狀態轉換 

線程的可運行狀態並不代表線程一定在運行(runnable != running ) 。 大家都知道:所有現代桌面和服務器操作系統都使用了搶占式的線程調度策略 。一旦線程開始執行,並不是總是保持持續運行狀態的。當系統分給它的時間片(非常小的運行時間單位)用完以后,不管程序有沒有執行完,線程被強制放棄CPU,進入就緒狀態,直到下次被調度后開始繼續執行。也就是說, Runnable可運行狀態的線程處於兩種可能的情況下:(1)占用CPU運行中,(2)等待調度的就緒狀態。 這里要聲明一下:處於等待調度的就緒狀態線程和處於阻塞的線程是完全不同的。就緒的線程是因為時間片用完而放棄CPU,其隨時都有可能再次獲得CPU而運行,這一切取決於分時OS的線程調度策略。

在很多操作系統的專業術語中,這種因時間片用完而被剝奪CPU的情況我們叫做線程中斷 。注意這和我們下面要將得中斷線程是兩個完全不同的概念。事實上,我們不可能通過應用程序來控制CPU的線程中斷,除非我們能夠自由調用OS的內核。
中斷可以理解為線程的一個標識位屬性,表示一個運行中的線程是否被其他線程進行了中斷操作。中斷好比其他線程對該線程打了一個招呼,其他線程通過調用該線程的interrupt()方法對其進行中斷操作。

一個正在運行的線程除了正常的時間片中斷之外,能否被其他線程控制?或者說其他線程能否讓指定線程放棄CPU或者提前結束運行? 除了線程同步機制之外,還有兩種方法:
(1) stop(), suspend(), resume() 和Runtime.runFinalizersOnExit() ,但這些方法已經被廢棄。
(2) interrupt() 方法

例:創建了一個線程countThread,它不斷地進行變量累加,而主線程嘗試對其進行中斷操作和停止操作。

import java.util.concurrent.TimeUnit;

public class Shutdown {
    public static void main(String[] args) throws Exception {
        Runner one = new Runner();
        Thread countThread = new Thread(one, "CountThread");
        countThread.start();
        // 睡眠1秒,main線程對CountThread進行中斷,使CountThread能夠感知中斷而結束
        TimeUnit.SECONDS.sleep(1); //Thread.sleep(1000);
 countThread.interrupt();
        System.out.println("是否停止1:" + countThread.isInterrupted());
        Runner two = new Runner();
        countThread = new Thread(two, "CountThread");
        countThread.start();
        // 睡眠1秒,main線程對Runner two進行取消,使CountThread能夠感知on為false而結束
        TimeUnit.SECONDS.sleep(1);
        two.cancel();
        System.out.println("是否停止2:" + countThread.isInterrupted());
    }

    private static class Runner implements Runnable {
        private long i;

        private volatile boolean on = true;

        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()) {
                i++;
            }
            System.out.println("Count i = " + i);
        }

        public void cancel() {
            on = false;
        }
    }
}

運行結果:

是否停止1:true
Count i = 574867187
是否停止2:false
Count i = 581254533

main線程通過中斷操作和cancel()方法均可使線程countThread終止。
當我們調用countThread.interrput()的時候,線程countThread的中斷狀態(interrupted status) 會被置位。我們可以通過Thread.currentThread().isInterrupted() 來檢查這個布爾型的中斷狀態。

在Core Java中有這樣一句話:"沒有任何語言方面的需求要求一個被中斷的程序應該終止。中斷一個線程只是為了引起該線程的注意,被中斷線程可以決定如何應對中斷 "。

while循環有一個決定因素就是需要不停的檢查自己的中斷狀態。當外部線程調用該線程的interrupt 時,使得中斷狀態置位。這時該線程將終止循環,不再執行循環中的內容。

這說明: interrupt中斷的是線程的某一部分業務邏輯,前提是線程需要檢查自己的中斷狀態(isInterrupted())。

參考:

java線程之中斷線程Interrupted用法 

《Java並發編程的藝術》

另:Thread.sleep和TimeUnit.SECONDS.sleep的區別與聯系 

二、interrupt()、interrupted() 和 isInterrupted()方法的區別

1、interrupt()方法
interrupt()方法用於中斷線程。調用該方法的線程的狀態為將被置為"中斷"狀態。
中斷可以理解為線程的一個標識位屬性,它表示一個運行中的線程是否被其他線程進行了中斷操作。中斷好比其他線程對該線程打了個招呼,其他線程通過調用該線程的interrupt()方法對其進行中斷操作。
注意:線程中斷僅僅是置線程的中斷狀態位,不會停止線程。需要用戶自己去監視線程的狀態為並做處理。

2、interrupted() 和 isInterrupted()

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

public boolean isInterrupted() {
    return isInterrupted(false);
}

private native boolean isInterrupted(boolean ClearInterrupted);

從源代碼可以看出:
interrupted()測試的是當前線程(current thread)的中斷狀態,且這個方法會清除中斷狀態。是靜態方法(它測試的是當前線程的中斷狀態)
isInterrupted()測試的是調用該方法的對象所表示的線程,且這個方法不會清除中斷狀態。是實例方法(它測試的是實例對象所表示的線程的中斷狀態)

關於方法isInterrupted( boolean ClearInterrupted):
通過參數名ClearInterrupted可以知道,這個參數代表是否要清除狀態位。
如果這個參數為true,說明返回線程的狀態位后,要清掉原來的狀態位(恢復成原來情況)。這個參數為false,就是直接返回線程的狀態位。

這兩個方法很好區分,只有當前線程才能清除自己的中斷位(對應interrupted()方法)

例:1. interrupted()方法

 1 public class InterruptedTest {
 2     public static void main(String[] args) throws InterruptedException {
 3         MyThread thread = new MyThread();
 4         thread.start();
 5         Thread.sleep(1000);
 6 
 7         System.out.println("當前正在執行的線程:" + Thread.currentThread().getName());
 8 
 9         thread.interrupt(); // Thread.currentThread().interrupt();
10 
11         System.out.println("是否停止?=" + thread.interrupted());// false,main線程沒有被中斷
12     }
13 }
14 
15 class MyThread extends Thread {
16     @Override
17     public void run() {
18         int i = 0;
19         super.run();
20         for (; i < 500000; i++) {
21             i++;
22         }
23         System.out.println("i: " + i);
24     }
25 }

運行結果:

i: 500000
當前正在執行的線程:main
是否停止?=false

第4行啟動thread線程,第5行使main線程睡眠1秒鍾從而使得thread線程有機會獲得CPU執行。
main線程睡眠1s鍾后,恢復執行到第7行,請求中斷 thread線程。
第11行測試線程是否處於中斷狀態,這里測試的是哪個線程呢?答案是main線程。因為:

(1)interrupted()測試的是當前的線程的中斷狀態

(2)main線程執行了第11行語句,故main線程是當前線程

如果將第9行換成Thread.currentThread().interrupt();,則運行結果為:

i: 500000
當前正在執行的線程:main
是否停止?=true

2. isInterrupted()方法

 1 public class InterruptedTest {
 2     public static void main(String[] args) throws InterruptedException {
 3         MyThread thread = new MyThread();
 4         thread.start();
 5         //Thread.sleep(1000);
 6 
 7         System.out.println("當前正在執行的線程:" + Thread.currentThread().getName());
 8 
 9         thread.interrupt(); 
10 
11         System.out.println("是否停止?=" + thread.isInterrupted());// false,main線程沒有被中斷
12     }
13 }
14 
15 class MyThread extends Thread {
16     @Override
17     public void run() {
18         int i = 0;
19         super.run();
20         for (; i < 500000; i++) {
21             i++;
22         }
23         System.out.println("i: " + i);
24     }
25 }

運行結果:

當前正在執行的線程:main
是否停止?=true
i: 500000

在第11行,是thread對象調用的isInterrupted()方法。因此,測試的是thread對象所代表的線程的中斷狀態。由於在第9行,main線程請求中斷 thread線程,故在第11行的結果為: true

 

另外:當線程被阻塞的時候,比如被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞時。調用它的interrput()方法。可想而知,沒有占用CPU運行的線程是不可能給自己的中斷狀態置位的。這就會產生一個InterruptedException異常。例:

public class InterruptedSleep {
    public static void main(String[] args) {
        Runnable tr = new TestRunnable();
        Thread th1 = new Thread(tr);
        th1.start(); // 開始執行分線程
        while (true) {
            th1.interrupt(); // 中斷這個分線程
        }
    }
}

class TestRunnable implements Runnable {
    public void run() {
        try {
            Thread.sleep(1000000); // 這個線程將被阻塞1000秒
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("是否停止:" + Thread.currentThread().isInterrupted());
        }
    }
}

運行結果:

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at threadArt.TestRunnable.run(InterruptedSleep.java:17)
    at java.lang.Thread.run(Thread.java:745)
是否停止:true

因此:

要中斷一個Java線程,可調用線程類(Thread)對象的實例方法:interrupte();然而interrupte()方法並不會立即執行中斷操作;具體而言,這個方法只會給線程設置一個為true的中斷標志(中斷標志只是一個布爾類型的變量),而設置之后,則根據線程當前的狀態進行不同的后續操作

1. 如果線程的當前狀態處於非阻塞狀態,那么僅僅是線程的中斷標志被修改為true而已

2. 如果線程的當前狀態處於阻塞狀態,那么在將中斷標志設置為true后,還會有如下三種情況之一的操作:

(1) 如果是wait、sleep以及jion三個方法引起的阻塞,那么會將線程的中斷標志重新設置為false,並拋出一個InterruptedException
(2) 如果是java.nio.channels.InterruptibleChannel進行的io操作引起的阻塞,則會對線程拋出一個ClosedByInterruptedException;(待驗證)
(3) 如果是輪詢(java.nio.channels.Selectors)引起的線程阻塞,則立即返回,不會拋出異常。(待驗證)
如果在中斷時,線程正處於非阻塞狀態,則將中斷標志修改為true,而在此基礎上,一旦進入阻塞狀態,則按照阻塞狀態的情況來進行處理;例如,一個線程在運行狀態中,其中斷標志被設置為true,則此后,一旦線程調用了wait、jion、sleep方法中的一種,立馬拋出一個InterruptedException,且中斷標志被清除,重新設置為false
通過上面的分析,我們可以總結,調用線程類的interrupted方法,其本質只是設置該線程的中斷標志,將中斷標志設置為true,並根據線程狀態決定是否拋出異常。因此,通過interrupted方法真正實現線程的中斷原理是:開發人員根據中斷標志的具體值,來決定如何退出線程。

參考:
JAVA多線程之中斷機制(stop()、interrupted()、isInterrupted())
使用interrupte中斷線程的真正用途 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM