国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

線程的中斷(interrupt)機制

2019-11-08 02:08:49
字體:
來源:轉載
供稿:網友

前言

在本博文的一篇如何正確的關閉一個線程一文中講解了如何利用interrupt機制來中斷一個線程,這篇文章當時確實花了一些精力的總結,不過都是15年末的事情了,現在是2017年2月份,經過一年的時間,決定重新寫一篇完善的關于線程中斷的文章。

什么時候需要關閉一個線程?

下面簡單的舉例情況:

比如我們會啟動多個線程做同一件事,比如搶12306的火車票,我們可能開啟多個線程從多個渠道買火車票,只要有一個渠道買到了,我們會通知取消其他渠道。這個時候需要關閉其他線程很多線程的運行模式是死循環,比如在生產者/消費者模式中,消費者主體就是一個死循環,它不停的從隊列中接受任務,執行任務,在停止程序時,我們需要一種”優雅”的方法以關閉該線程在一些場景中,比如從第三方服務器查詢一個結果,我們希望在限定的時間內得到結果,如果得不到,我們會希望取消該任務??傊?,很多情況下我們都有關閉一個線程的需求,那么如何正確的關閉一個線程就是我們要研究的事情,這個事情在上一篇文章中已經討論過了,這里不在贅述。

廢棄的API

Thread.STOP()之類的api會造成一些不可預知的bug,所以很早便DePRecated了,真要糾結為什么請看這邊文章為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?

線程中斷API

Thread類定義了如下關于中斷的方法:

API作用
public static boolean interrupted就是返回對應線程的中斷標志位是否為true返回當前線程的中斷標志位是否為true,但它還有一個重要的副作用,就是清空中斷標志位,也就是說,連續兩次調用interrupted(),第一次返回的結果為true,第二次一般就是false (除非同時又發生了一次中斷)。
public boolean isInterrupted()就是返回對應線程的中斷標志位是否為true
public void interrupt()表示中斷對應的線程

線程對中斷的反應

RUNNABLE:線程在運行或具備運行條件只是在等待操作系統調度WAITING/TIMED_WAITING:線程在等待某個條件或超時BLOCKED:線程在等待鎖,試圖進入同步塊NEW/TERMINATED:線程還未啟動或已結束

RUNNABLE狀態

如果線程在運行中,interrupt()只是會設置線程的中斷標志位,沒有任何其它作用。線程應該在運行過程中合適的位置檢查中斷標志位,比如說,如果主體代碼是一個循環,可以在循環開始處進行檢查,如下所示:

123456789101112131415
public class InterruptRunnableDemo extends Thread {    @Override    public void run() {        while (!Thread.currentThread().isInterrupted()) {            // ... 單次循環代碼        }        System.out.println("done ");    }    public static void main(String[] args) throws InterruptedException {        Thread thread = new InterruptRunnableDemo();        thread.start();        Thread.sleep(1000);        thread.interrupt();    }}

WAITING/TIMED_WAITING

線程執行如下方法會進入WAITING狀態:

12
public final void join() throws InterruptedExceptionpublic final void wait() throws InterruptedException

執行如下方法會進入TIMED_WAITING狀態:

123
public final native void wait(long timeout) throws InterruptedException;public static native void sleep(long millis) throws InterruptedException;public final synchronized void join(long millis) throws InterruptedException

在這些狀態時,對線程對象調用interrupt()會使得該線程拋出InterruptedException,需要注意的是,拋出異常后,中斷標志位會被清空(線程的中斷標志位會由true重置為false,因為線程為了處理異常已經重新處于就緒狀態。),而不是被設置。比如說,執行如下代碼:

1234567891011121314151617
Thread t = new Thread (){    @Override    public void run() {        try {            Thread.sleep(1000);        } catch (InterruptedException e) {        //exception被捕獲,但是為輸出為false 因為標志位會被清空            System.out.println(isInterrupted());        }    }        };t.start();try {    Thread.sleep(100);} catch (InterruptedException e) {}t.interrupt();//置為true

InterruptedException是一個受檢異常,線程必須進行處理。我們在異常處理中介紹過,處理異常的基本思路是,如果你知道怎么處理,就進行處理,如果不知道,就應該向上傳遞,通常情況下,你不應該做的是,捕獲異常然后忽略。

捕獲到InterruptedException,通常表示希望結束該線程,線程大概有兩種處理方式:

向上傳遞該異常,這使得該方法也變成了一個可中斷的方法,需要調用者進行處理有些情況,不能向上傳遞異常,比如Thread的run方法,它的聲明是固定的,不能拋出任何受檢異常,這時,應該捕獲異常,進行合適的清理操作,清理后,一般應該調用Thread的interrupt方法設置中斷標志位,使得其他代碼有辦法知道它發生了中斷

第一種方式的示例代碼如下:

12345
//拋出中斷異常,由調用者捕獲public void interruptibleMethod() throws InterruptedException{    // ... 包含wait, join 或 sleep 方法    Thread.sleep(1000);}

第二種方式的示例代碼如下:

123456789101112131415161718192021222324252627
public class InterruptWaitingDemo extends Thread {    @Override    public void run() {        while (!Thread.currentThread().isInterrupted()) {            try {                // 模擬任務代碼                Thread.sleep(2000);            } catch (InterruptedException e) {                // ... 清理操作                System.out.println(isInterrupted());//false                // 重設中斷標志位為true                Thread.currentThread().interrupt();            }        }        System.out.println(isInterrupted());//true    }    public static void main(String[] args) {        InterruptWaitingDemo thread = new InterruptWaitingDemo();        thread.start();        try {            Thread.sleep(100);        } catch (InterruptedException e) {        }        thread.interrupt();    }}

BLOCKED

如果線程在等待鎖,對線程對象調用interrupt()只是會設置線程的中斷標志位,線程依然會處于BLOCKED狀態,也就是說,interrupt()并不能使一個在等待鎖的線程真正”中斷”。我們看段代碼:

1234567891011121314151617181920212223242526
public class InterruptWaitingDemo extends Thread {    @Override    public void run() {        while (!Thread.currentThread().isInterrupted()) {            try {                // 模擬任務代碼                Thread.sleep(2000);            } catch (InterruptedException e) {                // ... 清理操作                // 重設中斷標志位                Thread.currentThread().interrupt();            }        }        System.out.println(isInterrupted());    }    public static void main(String[] args) {        InterruptWaitingDemo thread = new InterruptWaitingDemo();        thread.start();        try {            Thread.sleep(100);        } catch (InterruptedException e) {        }        thread.interrupt();    }}

BLOCKED如果線程在等待鎖,對線程對象調用interrupt()只是會設置線程的中斷標志位,線程依然會處于BLOCKED狀態,也就是說,interrupt()并不能使一個在等待鎖的線程真正”中斷”。我們看段代碼:

1234567891011121314151617181920212223242526272829
public class InterruptSynchronizedDemo {    private static Object lock = new Object();//monitor    private static class A extends Thread {        @Override        public void run() {        	//等待lock鎖            synchronized (lock) {            		//等待標志位被置為true                while (!Thread.currentThread().isInterrupted()) {                }            }            System.out.println("exit");        }    }    public static void test() throws InterruptedException {        synchronized (lock) {//獲取鎖            A a = new A();            a.start();            Thread.sleep(1000);			//a在等待lock鎖,interrupt 無法中斷            a.interrupt();            //a線程加入當前線程,等待執行完畢            a.join();        }    }    public static void main(String[] args) throws InterruptedException {        test();    }}

test方法在持有鎖lock的情況下啟動線程a,而線程a也去嘗試獲得鎖lock,所以會進入鎖等待隊列,隨后test調用線程a的interrupt方法并等待線程線程a結束,線程a會結束嗎?不會,interrupt方法只會設置線程的中斷標志,而并不會使它從鎖等待隊列中出來。

我們稍微修改下代碼,去掉test方法中的最后一行a.join,即變為:

123456789
public static void test() throws InterruptedException {    synchronized (lock) {        A a = new A();        a.start();        Thread.sleep(1000);        a.interrupt();    }    //lock鎖釋放后 A線程重隊列中出來}

這時,程序就會退出。為什么呢?因為主線程不再等待線程a結束,釋放鎖lock后,線程a會獲得鎖,然后檢測到發生了中斷,所以會退出。

在使用synchronized關鍵字獲取鎖的過程中不響應中斷請求,這是synchronized的局限性。如果這對程序是一個問題,應該使用顯式鎖,java中的Lock接口,它支持以響應中斷的方式獲取鎖。對于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中斷的加鎖操作,它可以拋出中斷異常。等同于等待時間無限長的Lock.tryLock(long time, TimeUnit unit)。

NEW/TERMINATE

如果線程尚未啟動(NEW),或者已經結束(TERMINATED),則調用interrupt()對它沒有任何效果,中斷標志位也不會被設置。比如說,以下代碼的輸出都是false。

12345678910111213141516171819202122
public class InterruptNotAliveDemo {    private static class A extends Thread {        @Override        public void run() {        }    }    public static void test() throws InterruptedException {        A a = new A();        a.interrupt();        System.out.println(a.isInterrupted());        a.start();        Thread.sleep(100);        a.interrupt();        System.out.println(a.isInterrupted());    }    public static void main(String[] args) throws InterruptedException {        test();    }}

IO操作

如果線程在等待IO操作,尤其是網絡IO,則會有一些特殊的處理,我們沒有介紹過網絡,這里只是簡單介紹下。

實現此InterruptibleChannel接口的通道是可中斷的:如果某個線程在可中斷通道上因調用某個阻塞的 I/O 操作(常見的操作一般有這些:serverSocketChannel. accept()、socketChannel.connect、socketChannel.open、socketChannel.read、socketChannel.write、fileChannel.read、fileChannel.write)而進入阻塞狀態,而另一個線程又調用了該阻塞線程的 interrupt 方法,這將導致該通道被關閉,并且已阻塞線程接將會收到ClosedByInterruptException,并且設置已阻塞線程的中斷狀態。另外,如果已設置某個線程的中斷狀態并且它在通道上調用某個阻塞的 I/O 操作,則該通道將關閉并且該線程立即接收到 ClosedByInterruptException;并仍然設置其中斷狀態。如果線程阻塞于Selector調用,則線程的中斷標志位會被設置,同時,阻塞的調用會立即返回。

我們重點介紹另一種情況,InputStream的read調用,該操作是不可中斷的,如果流中沒有數據,read會阻塞 (但線程狀態依然是RUNNABLE),且不響應interrupt(),與synchronized類似,調用interrupt()只會設置線程的中斷標志,而不會真正”中斷”它,我們看段代碼

1234567891011121314151617181920212223
public class InterruptReadDemo {    private static class A extends Thread {        @Override        public void run() {            while(!Thread.currentThread().isInterrupted()){                try {                    System.out.println(System.in.read())//wait input                } catch (IOException e) {                    e.printStackTrace();                }                }            System.out.println("exit");        }    }    public static void main(String[] args) throws InterruptedException {        A t = new A();        t.start();        Thread.sleep(100);        t.interrupt();    }}

線程t啟動后調用System.in.read()從標準輸入讀入一個字符,不要輸入任何字符,我們會看到,調用interrupt()不會中斷read(),線程會一直運行。

不過,有一個辦法可以中斷read()調用,那就是調用流的close方法,我們將代碼改為:

12345678910111213141516171819202122232425262728
public class InterruptReadDemo {    private static class A extends Thread {        @Override        public void run() {            while (!Thread.currentThread().isInterrupted()) {                try {                    System.out.println(System.in.read());                } catch (IOException e) {                    e.printStackTrace();                }            }            System.out.println("exit");        }        public void cancel() {            try {                System.in.close();            } catch (IOException e) {            }            interrupt();        }    }    public static void main(String[] args) throws InterruptedException {        A t = new A();        t.start();        Thread.sleep(100);        t.cancel();    }}

我們給線程定義了一個cancel方法,在該方法中,調用了流的close方法,同時調用了interrupt方法,這次,程序會輸出:

12
-1exit

也就是說,調用close方法后,read方法會返回,返回值為-1,表示流結束。

如何正確地取消/關閉線程

以上,我們可以看出,interrupt方法不一定會真正”中斷”線程,它只是一種協作機制,如果不明白線程在做什么,不應該貿然的調用線程的interrupt方法,以為這樣就能取消線程。對于以線程提供服務的程序模塊而言,它應該封裝取消/關閉操作,提供單獨的取消/關閉方法給調用者,類似于InterruptReadDemo中演示的cancel方法,外部調用者應該調用這些方法而不是直接調用interrupt。Java并發庫的一些代碼就提供了單獨的取消/關閉方法,比如說,Future接口提供了如下方法以取消任務:boolean cancel(boolean mayInterruptIfRunning);

再比如,ExecutorService提供了如下兩個關閉方法:

12
void shutdown();List<Runnable> shutdownNow();

Future和ExecutorService的API文檔對這些方法都進行了詳細說明,這是我們應該學習的方式。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 安阳县| 湘潭市| 渝北区| 巫山县| 会泽县| 阳城县| 台湾省| 阳朔县| 崇左市| 武定县| 伽师县| 蒲城县| 天祝| 白城市| 上饶县| 平潭县| 泰兴市| 津南区| 三原县| 顺昌县| 西藏| 申扎县| 嘉峪关市| 白银市| 盐边县| 沙河市| 新巴尔虎左旗| 武清区| 白水县| 安阳县| 延庆县| 黔南| 镇雄县| 韶山市| 专栏| 禹城市| 双城市| 蓬溪县| 嵩明县| 子长县| 丹江口市|