构建更健壮的系统:如何干掉死循环的线程
程序员文章站
2022-01-22 10:04:13
...
为什么要干掉一个线程,这个和更健壮的系统有什么关系。
有时候我会想到一个问题,开发的系统基本上就是自己/测试团队测试了几遍然后上线。
上线后出现了不可预知的bug怎么办呢,例如出现了死循环或者部分逻辑有问题导致用户点击后会找出非常长的时间等待而且没办法进行其他操作。
java里面很多业务都用到了多线程,业务都放在线程池里面写
Thread.interrupt() 是无法终止一个非阻塞线程的,只能用Thread.stop()
stop()方法又不是推荐方法,不安全并@Deprecated了 但是又只有stop这个方法才能解决死循环
那么我们可以折中下,万不得已的情况才stop()
核心代码:先判断线程的状态,超时的线程又是RUNNABLE状态肯定是死循环了,其他情况使用中断方法。
// 获取到了执行该任务的线程 Thread execThread = (Thread) runner.get(obj); if (execThread == null) { return; } State state = execThread.getState(); if (state == State.RUNNABLE) { execThread.stop();// 非阻塞线程才STOP,例如死循环 } else { // 其他情况使用interrupt()方式,例如sleep、wait等等 } future.cancel(true);
完整代码:中断线程池里面的一个FutureTask
public void killThread(FutureTask<?> future) { try { if (future.isDone()) { return; } // 处理长时间超时的线程 // 利用反射,强行取出正在运行该任务的线程 // Thread属性藏在FutureTask.Sync.runner属性,因为Sync是私有类需要多反射一次 Field sync = FutureTask.class.getDeclaredField("sync"); sync.setAccessible(true); Object obj = sync.get(future); Field runner = obj.getClass().getDeclaredField("runner"); runner.setAccessible(true); // 获取到了执行该任务的线程 Thread execThread = (Thread) runner.get(obj); if (execThread == null) { return; } State state = execThread.getState(); if (state == State.RUNNABLE) { execThread.stop();// 非阻塞线程才STOP,例如死循环 } else { // 其他情况使用interrupt()方式,例如sleep、wait等等 } future.cancel(true); } catch (Exception e) { e.printStackTrace(); } }
完整Class:
/** * * 安全线程池,一个线程超时timeOut时间就杀掉这个线程。保证整个服务的稳定 */ public class SafeExecutorService extends ScheduledThreadPoolExecutor { Logger logger = LoggerFactory.getLogger(SafeExecutorService.class); public SafeExecutorService(int corePoolSize) { super(corePoolSize); } long timeOut = 5;// 超时时间 TimeUnit timeUnit = TimeUnit.MINUTES; ScheduledExecutorService timer = Executors .newSingleThreadScheduledExecutor(); public SafeExecutorService(int corePoolSize, long timeOut, TimeUnit timeUnit) { super(corePoolSize); this.timeOut = timeOut; this.timeUnit = timeUnit; } @Override public Future<?> submit(Runnable task) { final FutureTask<?> future = (FutureTask<?>) super.submit(task); timer.schedule(new Runnable() { @Override public void run() { killThread(future); } }, timeOut, timeUnit); return future; } /** * * 真正的终止任务 */ public void killThread(FutureTask<?> future) { try { if (future.isDone()) { return; } // 处理长时间超时的线程 // 利用反射,强行取出正在运行该任务的线程 // Thread属性藏在FutureTask.Sync.runner属性,因为Sync是私有类需要多反射一次 Field sync = FutureTask.class.getDeclaredField("sync"); sync.setAccessible(true); Object obj = sync.get(future); Field runner = obj.getClass().getDeclaredField("runner"); runner.setAccessible(true); // 获取到了执行该任务的线程 Thread execThread = (Thread) runner.get(obj); if (execThread == null) { return; } State state = execThread.getState(); if (state == State.RUNNABLE) { execThread.stop();// 非阻塞线程才STOP,例如死循环 } else { // 其他情况使用interrupt()方式,例如sleep、wait等等 } future.cancel(true); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { SafeExecutorService sss = new SafeExecutorService(2, 1, TimeUnit.SECONDS); sss.submit(new Runnable() { @Override public void run() { while (true) { System.out.println(1); } } }); sss.submit(new Runnable() { @Override public void run() { try { Thread.sleep(1000 * 500); } catch (InterruptedException e) { e.printStackTrace(); } } }); } }
简单的介绍一下就是在执行一个任务的时候开计时器,时间到了就判断这个任务是否已经完成。
没完成的就肯定是有问题的,需要杀掉这个任务防止影响整个服务。
这种做法可以用在系统的构建上,来确保不会因为业务影响所有的服务。