487
技術社區[雲棲]
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