线上 cpu 负载过高 - java 原生 interrupt 使用不当

最近在排查老项目的线上问题时,发现一个可能容易忽略的问题,java 原生的 interrupt,大概逻辑是这样的:

  1. 首先会启动一个线程去执行 formatTransporter() 方法
  2. 另外会有一个线程再一段时间后调用 stop 方法,中断格式化转发任务
    核心代码如下:
private Thread t;

// 格式化转发任务
public void formatTransporter() {
    // 格式化转发
    t = new Thread(() -> {
        while (true) {
            if (tempQueue.size() > 0) {
                // 模拟格式化转发
                tempQueue.poll();
            }
        });
    t.start();
}

// 另外会有一个线程再一段时间后调用 stop 方法
public void stop() {
    while (true) {
        if (tempQueue.size() == 0 && t != null) {
            t.interrupt();
            break;
        }
    }
}

那么这样些会不会有什么问题呢?这就需要说一下 java 中的中断到底干了什么

中断:其实就是一个状态位,默认为 false,当你调用 interrupt 方法时,会将中断状态置为 true,当线程因为调用 sleep,wait 等方法被阻塞时,会抛出 InterruptedException,这时中断状态位会置为 false

从上面所说可以看出,调用 interrupt 后并没有把线程终止,所以需要我们自己去处理,将 formatTransporter 修改如下,增加被中断时跳出循环,使线程正常结束,如果发生中断异常,也需要进行处理,使线程可以结束

public void formatTransporter() {
    // 格式化转发
    t = new Thread(() -> {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                break;
            }
            if (tempQueue.size() > 0) {
                // 模拟格式化转发
                tempQueue.poll();
            }
        });
    t.start();
}

当然,推荐使用线程池执行任务

完整代码:

/**
 * 1、项目启动的时候启动
 * udp server:接收业务数据写入 queue
 * 数据处理任务:处理 queue 中数据,将需要格式化转发的写入新的队列 tempQueue
 * 格式化传输任务:数据处理时启动,超过接收数据时间后销毁
 *
 * @author IDOL小豆子
 * @since 2022-11-04 09:54:22
 */
public class InterruptDemo1 {

    private final LinkedBlockingQueue<String> dataQueue = new LinkedBlockingQueue<>();

    private final LinkedBlockingQueue<String> tempQueue = new LinkedBlockingQueue<>();

    private Thread t;

    /**
     * 当前批次是否发送完, 默认为 true
     */
    private final AtomicBoolean flag = new AtomicBoolean(true);

    ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
    
    public void sendData2Queue() {
        while (true) {
            dataQueue.offer("1");
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void dataHandler() {
        while (true) {
            if (dataQueue.size() == 0) {
                continue;
            }
            String s = dataQueue.poll();
            // 启动格式化转发线程
            if (flag.get()) {
                System.out.println("启动格式化转发线程");
                t = new Thread(this::formatTransporter, "format-transporter-thread");
                t.start();
                flag.set(false);
                // 模拟计划结束,中断格式化线程
                scheduledThreadPoolExecutor.schedule(() -> {
                    stop();
                    flag.set(true);
                }, 3, TimeUnit.SECONDS);
            }
            // 处理数据
            // 格式化转发
            tempQueue.offer(s);
        }
    }

    public void formatTransporter() {
//        while (!Thread.currentThread().isInterrupted()) {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                break;
            }
            if (tempQueue.size() > 0) {
                // 模拟格式化转发
                tempQueue.poll();
            }
        }
    }

    public void stop() {
        while (true) {
            if (tempQueue.size() == 0 && t != null) {
                t.interrupt();
                break;
            }
        }
    }

    public static void main(String[] args) {
        InterruptDemo1 interruptDemo1 = new InterruptDemo1();
        new Thread(interruptDemo1::sendData2Queue, "udp-server").start();
        new Thread(interruptDemo1::dataHandler, "data-handler").start();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值