線程取消與關閉

Java沒有安全的搶占方法來停止線程,只有協作式的機制,使代碼遵循一種協商好的協議來提前結束線程。

其中一種協作機制是通過設置標志(已請求取消),任務將定期查看該標志,如果設置了該標志,則任務提前結束。

例如我們可以通過volatile類型域(關于volatitle類型域的特點和作用本篇不做介紹,不了解的同學可以自行搜索)來取消,設置變量private volatile boolean cancelled = false;
在任務中循環判斷cancelled來跳出循環

 private volatile boolean cancelled = false;
 while(!cancelled){
    System.out.println(" queue.put 1");
    queue.put(p = p.nextProbablePrime());
    System.out.println(" queue.put 2");
}

public void cancel(){
//      interrupt();
    cancelled = true;
    System.out.println(" cancel ");
}

但如果使用這個方法的任務中調用了阻塞方法,可能會因為阻塞而存在代碼永遠不會執行到檢查標志位的地方,因此永遠不會結束(一直卡在阻塞位置)
LOG如下:
queue.put 1
queue.put 2
queue.put 1
queue.put 2
queue.put 1
queue.put 2
queue.put 1
queue.put 2
queue.put 1
consume value = 2
cancel

所以保險情況是通過中斷來取消

完整代碼如下

package com.thread.test.interrupt;

import java.math.BigInteger;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import static java.util.concurrent.TimeUnit.SECONDS;

public class PrimeProducer extends Thread {
    private final BlockingQueue<BigInteger> queue;
    private volatile boolean cancelled = false;
    public PrimeProducer(BlockingQueue<BigInteger> queue) {
        // TODO Auto-generated constructor stub
        this.queue = queue;
    }
    
    public void run() {
        try{
            BigInteger p = BigInteger.ONE;
//          // volatile類型域
//          while(!cancelled){
//              System.out.println(" queue.put 1");
//              queue.put(p = p.nextProbablePrime());
//              System.out.println(" queue.put 2");
//          }
            
//          while(!Thread.currentThread().isInterrupted()){
//              System.out.println(" queue.put 1");
//              queue.put(p = p.nextProbablePrime());
//              System.out.println(" queue.put 2");
//          }
            while(true){
                if(Thread.currentThread().isInterrupted()){
                    System.out.println(" isInterrupted ");
                    break;
                }
                System.out.println(" queue.put 1");
                queue.put(p = p.nextProbablePrime());
                System.out.println(" queue.put 2");
            }
        }catch(InterruptedException e){
            System.out.println(" InterruptedException e");
        }
    }
    
    public void cancel(){
        interrupt();
//      cancelled = true;
        System.out.println(" cancel ");
    }
    
    public static boolean needMroePrime(int count){
        if(count >= 1){
            return false;
        }else{
            return true;
        }
    }
    
    public static void consume(BigInteger value){
        System.out.println(" consume value = "+value);
    }
    
    public static void main(String[] args) {
        final BlockingQueue<BigInteger> prime = new ArrayBlockingQueue<BigInteger>(3);
        PrimeProducer producer = new PrimeProducer(prime);
        producer.start();
        int count = 0;
        try{
            SECONDS.sleep(1);
            while(needMroePrime(count)){
                count++;
                consume(prime.take());
            }
        }catch(InterruptedException e){
            
        }finally{
            producer.cancel();
        }
    }
    
    
}

程序運行結果示例:
queue.put 1
queue.put 2
queue.put 1
queue.put 2
queue.put 1
queue.put 2
queue.put 1
queue.put 2
queue.put 1
consume value = 2
cancel
InterruptedException e

從LOG執行情況我們可以看到,線程雖然還是阻塞在put方法中,但還是通過拋出異常而退出,進一步查看源碼我們可以發現在BlockingQueue的put方法中會拋出InterruptedException異常,并注明 InterruptedException if interrupted while waiting
(繼續深入源碼可以發現阻塞實際是一個while循環不斷執行,再循環中會判斷Thread.interrupted(),中斷則拋出InterruptedException, AbstractQueuedSynchronizer$ConditionObject)
另外一些阻塞庫方法,例如Thread.sleep和Object.wait等,都會檢查線程何時中斷,并再發現中斷時提前返回,清除中斷狀態并拋出異常。

我們得出如下結論:

通常,中斷是實現取消最合理的方式

其他中斷線程的方法:

Thread.stop(),此方法由于嚴重的缺陷以及被廢棄,非常不建議使用,具體原因大家可自行查找

為什么要使用拋出InterruptedException的方式:

  1. 首先,我們再次回顧一下線程取消的概念,是讓任務提前結束,提前結束的意思可以理解線程Run方法中沒有代碼需要執行
  2. 線程中斷本質上也是通過判斷標志位進而拋出InterruptedException異常,拋出異常后線程實際上還沒有退出,捕獲異常之后如果直接不處理或者在Run方法中直接return則線程就被取消了,如果還有一些操作(比如例子中的打印日志),代碼還是會執行,這里可以做一些中斷策略的設計,一般我們會用來復位一些標記位,做一些資源釋放的邏輯。
  3. 如果不使用拋出異常的方式會怎么樣?比如在循環中我們可以通過break提前退出,再方法函數中我們可以通過return提前退出,但異常更好,異常可以不斷向上拋出(避免多個函數嵌套如果不用異常則需要不停return),只有在捕獲的地方進行處理(代碼邏輯會更清晰),甚至可以處理之后繼續向上拋出,可以非常靈活的使用,這里可以參考這篇文章 https://www.cnblogs.com/greta/p/5624839.html

附錄
Thread中的中斷方法說明

public class Thread{
  public void interrupt(){...}
  public boolean isInterrupted(){...}
  public static boolean interrupted(){
    return currentThread().isInterrupted(true);
  }
  // 確定線程是否被中斷,根據ClearInterrupted值決定是否重置
  private native boolean isInterrupted(boolean ClearInterrupted);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容