最近在排查老项目的线上问题时,发现一个可能容易忽略的问题,java 原生的 interrupt,大概逻辑是这样的:
- 首先会启动一个线程去执行 formatTransporter() 方法
- 另外会有一个线程再一段时间后调用 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();
}
}