閱讀487 返回首頁    go 技術社區[雲棲]


Thread.interrupt() 使用不當,導致程序無法退出

https://blog.chenlb.com/2009/07/incorrect-use-thread-interrupt-cause-not-exit.html


Java Thread.interrupt() 使用不當,導致多線程程序無法正常退出。前段時間寫的一個多線程程序:一個子線程基本是死循環地從任務池裏取出任務(取的時候,沒有任務會阻塞),並運行可用的任務。沒有任務了,完成的時候 main 線程調用子線程的中斷,抓到中斷後退出子線程的死循環。

上麵的程序已經正常的運行了幾個月了,是一天運行一次而已。近期要在每4分鍾運行一次的應用中使用了這程序。運行一天多就會阻塞掉了(一直不會退出),今天是第二次了。觀察分析日誌:是在 main 調用 taskThread.interrupt() 會沒有輸出日誌(實際就是沒有中斷到子線程),而死循環是在接受到中斷才會把循環條件設為 false。所以程序 block了,不退出了。

分析:調用 taskThread.interrupt() 為什麼沒有接受到中斷(實際是 InterruptedException)呢?找原理:Thread.interrupt() 的調用對正在運行的線程是不起作用的,隻有對阻塞的線程有效。下麵是引用:Java Thread.interrupt 害人! 中斷JAVA線程(zz) 的一段重要的話。

使用Thread.interrupt()中斷線程

Thread.interrupt()方法不會中斷一個正在運行的線程。這一方法實際上完成的是,在線程受到阻塞時拋出一個中斷信號,這樣線程就得以退出阻塞的狀態。更確切的說,如果線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一個中斷異常(InterruptedException),從而提早地終結被阻塞狀態。

因此,如果線程被上述幾種方法阻塞,正確的停止線程方式是設置共享變量,並調用interrupt()(注意變量應該先設置)。如果線程沒有被阻塞,這時調用interrupt()將不起作用;否則,線程就將得到異常(該線程必須事先預備好處理此狀況),接著逃離阻塞狀態。在任何一種情況中,最後線程都將檢查共享變量然後再停止。

程序代碼塊大概:

public void run() {  
    while(running) {  
        Object obj = null;  
        try {  
            //...  
            //取出數據  
            obj = taskQueue.take();  
            //執行任務...  
        } catch (InterruptedException e) {  
            running = false;  
            log.info("被 interrupt, 退出");  
        }  
  
    }   //while  
    //....  
}  

描述現在的程序:使子線程阻塞的地方是從任務池取數據,具體來說是 java.util.concurrent.LinkedBlockingQueue.take() 方法。它在 try/catch 內,catch 到 InterruptedException 設置執行任務的循環條件變量。然後就退出 run。我草率地認為interrupt被調用了(認為take會被阻塞,有一段每200ms檢測全部任務是否完成),一定能 catch 到中斷。也沒有在調用 taskThread.interrupt() 之前把循環條件變量設為 false。所以造成有時候子線程不能退出。

當執行最後一個任務還沒有完成時,main 調用 taskThread.interrupt() 就會失效,因為子線程沒有 blocking。

解決辦法是在調用 interrupt 之前,running = false。


最後更新:2017-04-03 22:15:39

  上一篇:go 使用HttpURLConnection下載文件時出現 java.io.FileNotFoundException徹底解決辦法
  下一篇:go 對Thread.interrupt()方法很詳細的介紹