上周有个同事跑来问我,线程池里的任务退不出去,调了thread.interrupt()还是卡在那,日志也不报错。他当时以为中断就是强制杀线程,按一下就该停,结果等了两分钟还在跑。
其实interrupt只是给线程发个信号,相当于拍一下肩膀说可以收工了。线程收不收,取决于它自己有没有检查这个信号。如果代码里一直在死循环做计算,从不检查标志,也不进阻塞方法,那这个信号就被晾在一边了。
interrupt到底改了什么:中断标志位
每个线程内部有个中断标志,调interrupt就是把标志置起来。如果线程正卡在sleep、wait、join里面,会立刻被晃醒,同时抛出InterruptedException,并且顺手把标志清掉。如果没在阻塞,只是埋头跑,那标志就一直留着,等代码自己去查。
查标志有两个方法,一个是Thread.currentThread().isInterrupted(),不会清除标志,可以反复查。另一个是Thread.interrupted(),查完顺手清掉,适合只处理一次的场景。很多人混用这两个,结果第二次查不到,逻辑就乱了。
能停下来的写法:循环里主动让路
- 计算型循环:每次迭代查一次isInterrupted,收到就break,再收尾资源
- 阻塞型任务:抓住InterruptedException后别吞掉,要么直接返回,要么重新设置中断
- 别用stop:那个方法太粗暴,会把锁和数据留在半截状态,早就不推荐了
举个典型坑,catch到中断异常后只打日志,循环继续跑,等于把收工信号吃了。正确做法是跳出循环,关闭文件句柄或者连接,再把状态往外抛,让上层知道是被中断的。
while(!Thread.currentThread().isInterrupted()){ doOneBatch(); try{ Thread.sleep(200); }catch(InterruptedException e){ Thread.currentThread().interrupt(); break; }} cleanup();还有一种是Future.cancel(true),底层也是调interrupt。如果你的Callable里没有响应中断的点,cancel了也停不下来。这时候别怪框架,先给任务加检查点,每处理几百条数据就看一眼标志。
要不要用中断,取舍很简单。需要优雅收尾就用interrupt加配合检查,收尾能做干净。想立刻杀死基本做不到,真要强杀只能关进程。把中断当成商量,别当成命令,代码会好写很多。