線程中斷機(jī)制(interrupt)

優(yōu)雅的中斷線程,是一門藝術(shù)

總所周知耳幢,Thread.stop, Thread.suspend, Thread.resume 都已經(jīng)被廢棄了。因?yàn)樗鼈兲┝α伺菲。遣话踩木υ澹@種暴力中斷線程是一種不安全的操作,舉個(gè)栗子來(lái)說(shuō)明其可能造成的問(wèn)題:

public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        StopThread thread = new StopThread();
        thread.start();
        // 休眠1秒邢隧,確保線程進(jìn)入運(yùn)行
        Thread.sleep(1000);
        // 暫停線程
        thread.stop();
//      thread.interrupt();
        // 確保線程已經(jīng)銷毀
        while (thread.isAlive()) { }
        // 輸出結(jié)果
        thread.print();
    }

    private static class StopThread extends Thread {

        private int x = 0; private int y = 0;

        @Override
        public void run() {
            // 這是一個(gè)同步原子操作
            synchronized (this) {
                ++x;
                try {
                    // 休眠3秒,模擬耗時(shí)操作
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ++y;
            }
        }

        public void print() {
            System.out.println("x=" + x + " y=" + y);
        }
    }
}

上述代碼中店印,run方法里是一個(gè)同步的原子操作,x和y必須要共同增加府框,然而這里如果調(diào)用thread.stop()方法強(qiáng)制中斷線程吱窝,輸出如下:

x=1 y=0

沒(méi)有異常,也破壞了我們的預(yù)期迫靖。如果這種問(wèn)題出現(xiàn)在我們的程序中院峡,會(huì)引發(fā)難以預(yù)期的異常。因此這種不安全的方式很早就被廢棄了系宜。取而代之的是interrupt()照激,上述代碼如果采用thread.interrupt()方法,輸出結(jié)果如下:

x=1 y=1
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at ThreadTest$StopThread.run(ThreadTest.java:28)

x=1,y=1 這個(gè)結(jié)果是符合我們的預(yù)期盹牧,同時(shí)還拋出了個(gè)異常俩垃,這個(gè)異常下文詳說(shuō)。

interrupt() 它基于「一個(gè)線程不應(yīng)該由其他線程來(lái)強(qiáng)制中斷或停止汰寓,而是應(yīng)該由線程自己自行停止口柳。」思想有滑,是一個(gè)比較溫柔的做法跃闹,它更類似一個(gè)標(biāo)志位。其實(shí)作用不是中斷線程毛好,而是「通知線程應(yīng)該中斷了」望艺,具體到底中斷還是繼續(xù)運(yùn)行,應(yīng)該由被通知的線程自己處理肌访。

interrupt() 并不能真正的中斷線程找默,這點(diǎn)要謹(jǐn)記。需要被調(diào)用的線程自己進(jìn)行配合才行吼驶。也就是說(shuō)惩激,一個(gè)線程如果有被中斷的需求店煞,那么就需要這樣做:

  1. 在正常運(yùn)行任務(wù)時(shí),經(jīng)常檢查本線程的中斷標(biāo)志位咧欣,如果被設(shè)置了中斷標(biāo)志就自行停止線程浅缸。
  2. 在調(diào)用阻塞方法時(shí)正確處理InterruptedException異常。(例如:catch異常后就結(jié)束線程魄咕。)

先看下 Thread 類 interrupt 相關(guān)的幾個(gè)方法:

   // 核心 interrupt 方法
   public void interrupt() {
        if (this != Thread.currentThread()) // 非本線程衩椒,需要檢查權(quán)限
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // 僅僅設(shè)置interrupt標(biāo)志位
                b.interrupt(this);    // 調(diào)用如 I/O 操作定義的中斷方法
                return;
            }
        }
        interrupt0();
    }
    // 靜態(tài)方法,這個(gè)方法有點(diǎn)坑哮兰,調(diào)用該方法調(diào)用后會(huì)清除中斷狀態(tài)毛萌。
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    // 這個(gè)方法不會(huì)清除中斷狀態(tài)
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
   // 上面兩個(gè)方法會(huì)調(diào)用這個(gè)本地方法,參數(shù)代表是否清除中斷狀態(tài)
   private native boolean isInterrupted(boolean ClearInterrupted);

首先講 interrupt() 方法:

  1. interrupt 中斷操作時(shí)喝滞,非自身打斷需要先檢測(cè)是否有中斷權(quán)限阁将,這由jvm的安全機(jī)制配置;
  2. 如果線程處于sleep, wait, join 等狀態(tài)右遭,那么線程將立即退出被阻塞狀態(tài)做盅,并拋出一個(gè)InterruptedException異常;
  3. 如果線程處于I/O阻塞狀態(tài)窘哈,將會(huì)拋出ClosedByInterruptException(IOException的子類)異常吹榴;
  4. 如果線程在Selector上被阻塞,select方法將立即返回滚婉;
  5. 如果非以上情況图筹,將直接標(biāo)記 interrupt 狀態(tài);

注意:interrupt 操作不會(huì)打斷所有阻塞让腹,只有上述阻塞情況才在jvm的打斷范圍內(nèi)远剩,如處于鎖阻塞的線程,不會(huì)受 interrupt 中斷骇窍;

阻塞情況下中斷瓜晤,拋出異常后線程恢復(fù)非中斷狀態(tài),即 interrupted = false

public class ThreadTest {
    
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Task("1"));
        t.start();
        t.interrupt();
    }

    static class Task implements Runnable{
        String name;
        
        public Task(String name) {
            this.name = name;
        }
        
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("thread has been interrupt!");
            }
            System.out.println("isInterrupted: " + Thread.currentThread().isInterrupted());
            System.out.println("task " + name + " is over");
        }
    }
}

輸出:

thread has been interrupt!
isInterrupted: false
task 1 is over

調(diào)用Thread.interrupted() 方法后線程恢復(fù)非中斷狀態(tài)

public class ThreadTest {
    
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Task("1"));
        t.start();
        t.interrupt();
    }

    static class Task implements Runnable{
        String name;
        
        public Task(String name) {
            this.name = name;
        }
        
        @Override
        public void run() {
            System.out.println("first :" + Thread.interrupted());
            System.out.println("second:" + Thread.interrupted());
            System.out.println("task " + name + " is over");
        }
    }
}

輸出結(jié)果:

first :true
second:false
task 1 is over

上述兩種隱含的狀態(tài)恢復(fù)操作腹纳,是符合常理的活鹰,因?yàn)榫€程標(biāo)記為中斷后,用戶沒(méi)有真正中斷線程只估,必然將其恢復(fù)為false。理論上Thread.interrupted()調(diào)用后着绷,如果已中斷蛔钙,應(yīng)該執(zhí)行退出操作,不會(huì)重復(fù)調(diào)用荠医。

多線程系列目錄(不斷更新中):
線程啟動(dòng)原理
線程中斷機(jī)制
多線程實(shí)現(xiàn)方式
FutureTask實(shí)現(xiàn)原理
線程池之ThreadPoolExecutor概述
線程池之ThreadPoolExecutor使用
線程池之ThreadPoolExecutor狀態(tài)控制
線程池之ThreadPoolExecutor執(zhí)行原理
線程池之ScheduledThreadPoolExecutor概述
線程池的優(yōu)雅關(guān)閉實(shí)踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吁脱,一起剝皮案震驚了整個(gè)濱河市桑涎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兼贡,老刑警劉巖攻冷,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異遍希,居然都是意外死亡等曼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門凿蒜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)禁谦,“玉大人,你說(shuō)我怎么就攤上這事废封≈莶矗” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵漂洋,是天一觀的道長(zhǎng)遥皂。 經(jīng)常有香客問(wèn)我,道長(zhǎng)刽漂,這世上最難降的妖魔是什么演训? 我笑而不...
    開(kāi)封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮爽冕,結(jié)果婚禮上仇祭,老公的妹妹穿的比我還像新娘。我一直安慰自己颈畸,他們只是感情好乌奇,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著眯娱,像睡著了一般礁苗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上徙缴,一...
    開(kāi)封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天试伙,我揣著相機(jī)與錄音,去河邊找鬼于样。 笑死疏叨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的穿剖。 我是一名探鬼主播蚤蔓,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼糊余!你這毒婦竟也來(lái)了秀又?” 一聲冷哼從身側(cè)響起单寂,我...
    開(kāi)封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吐辙,沒(méi)想到半個(gè)月后宣决,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昏苏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年尊沸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捷雕。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡椒丧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出救巷,到底是詐尸還是另有隱情壶熏,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布浦译,位于F島的核電站棒假,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏精盅。R本人自食惡果不足惜帽哑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叹俏。 院中可真熱鬧妻枕,春花似錦、人聲如沸粘驰。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蝌数。三九已至愕掏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間顶伞,已是汗流浹背饵撑。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唆貌,地道東北人滑潘。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像锨咙,于是被迫代替她去往敵國(guó)和親众羡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345