生产问题-Java服务CPU飙升100%问题

文章讲述了运维人员遇到微服务CPU使用率飙升的问题,通过分析发现是由于FutureTask在超时后未能正确中断线程导致的。解决方案是在任务中加入线程中断检查点,确保及时响应中断请求。
摘要由CSDN通过智能技术生成

情况回顾

最近运维同学反馈有微服务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 方法检测到线程的中断状态被设置时,它会响应这个中断:

  1. 清除线程的中断状态(即将其重置为未中断)。
  2. 抛出 InterruptedException,表示线程被中断了。

这个机制允许线程在执行长时间运行的任务时,能够定期检查是否有中断请求,并且能够及时响应这些请求。在这段代码中,如果 futureTask 的执行线程在 sleep 期间被中断,它会抛出 InterruptedException,从而可以在 FutureTaskCallable 中捕获这个异常,并根据需要进行处理,比如清理资源或者提前结束任务。

在实际应用中,如果你的线程执行了一个长时间的任务,你应该定期检查中断状态,并在适当的时候响应中断,以确保你的线程能够及时优雅地停止执行。这通常是通过在循环中检查 Thread.interrupted() 或者在阻塞方法中捕获 InterruptedException 来实现的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值