情况回顾
最近运维同学反馈有微服务CPU飙升100%
,根据dump的日志文件排查出问题所在,在这里给大家做简单的分享,希望给大家工作上可以带来帮助。
核心伪代码分析
@SneakyThrows//包装成 RuntimeException ,骗过编译器,使得调用点可以不用显示处理异常信息。
public static void main(String[] args) {ExecutorService executor = Executors.newSingleThreadExecutor();FutureTask<Boolean> futureTask = new FutureTask<Boolean>(() -> {long l = 0;for (long i1 = 0; i1 < 10_000_000_000L; i1++) {l++;if (i1%1_000_000_000 == 0) {System.out.println("l = " + l);}}System.out.println("futureTask running end!");return true;});executor.execute(futureTask);try {futureTask.get(3, TimeUnit.SECONDS);// 3秒钟没结束直接关闭进程} catch (Exception e) {e.printStackTrace();futureTask.cancel(true);// 终止任务} finally {executor.shutdown();// 优雅关闭线程}System.out.println("main end");TimeUnit.SECONDS.sleep(20);
}
代码说明:实际的业务场景是在for循环里面做业务逻辑,尝试用 futureTask.get(3, TimeUnit.SECONDS)
获取任务执行结果,设置最多等待 3
秒。如果任务在这段时间之内没有执行完成,则会抛出异常。在尝试等待结果期间,如果超过 3
秒或其他异常发生,异常会被捕获,打印堆栈跟踪信息,并且尝试通过 futureTask.cancel(true)
方法来取消任务(参数 true
表示如果任务正在运行可以中断它)
CPU持续飙升原因
futureTask.cancel(true);
是Java并发API中取消一个正在执行的任务的方法调用,并不会中断线程,而且会给线程打上INTERRUPTED的标志,运行到线程可中断的节点时,线程会查看这个标志,发现是INTERRUPTED,则会close调线程。这里虽然用futureTask.cancel(true)
来终止任务,线程依旧在运行,无法进行终止,一直在循环执行业务,所以CPU会持续飙升。如下:
解决方案
在任务里面加入线程中断点sleep(1ms)
@SneakyThrows//包装成 RuntimeException ,骗过编译器,使得调用点可以不用显示处理异常信息。
public static void main(String[] args) {ExecutorService executor = Executors.newSingleThreadExecutor();FutureTask<Boolean> futureTask = new FutureTask<Boolean>(() -> {long l = 0;for (long i1 = 0; i1 < 10_000_000_000L; i1++) {l++;if (i1%1_000_000_000 == 0) {TimeUnit.MILLISECONDS.sleep(1);System.out.println("l = " + l);}}System.out.println("futureTask running end!");return true;});executor.execute(futureTask);try {futureTask.get(3, TimeUnit.SECONDS);// 3秒钟没结束直接关闭进程} catch (Exception e) {e.printStackTrace();futureTask.cancel(true);// 终止任务} finally {executor.shutdown();// 优雅关闭线程}System.out.println("main end");TimeUnit.SECONDS.sleep(20);
}
在这段代码中,加上TimeUnit.MILLISECONDS.sleep(1);
会触发中断检查。TimeUnit.MILLISECONDS.sleep(1);
是一个可能抛出 InterruptedException
的阻塞操作。当 sleep
方法检测到线程的中断状态被设置时,它会响应这个中断:
- 清除线程的中断状态(即将其重置为未中断)。
- 抛出
InterruptedException
,表示线程被中断了。
这个机制允许线程在执行长时间运行的任务时,能够定期检查是否有中断请求,并且能够及时响应这些请求。在这段代码中,如果 futureTask
的执行线程在 sleep
期间被中断,它会抛出 InterruptedException
,从而可以在 FutureTask
的 Callable
中捕获这个异常,并根据需要进行处理,比如清理资源或者提前结束任务。
在实际应用中,如果你的线程执行了一个长时间的任务,你应该定期检查中断状态,并在适当的时候响应中断,以确保你的线程能够及时优雅地停止执行。这通常是通过在循环中检查 Thread.interrupted()
或者在阻塞方法中捕获 InterruptedException
来实现的。