Java Timer/TimerTask - 如果未收到消息则触发事件
Java Timer/TimerTask - fire event if message not received
我有以下代码,其目的是在定期调用停止到达 messageReceived() 时增加普罗米修斯计数器:
...
private static final int tenMinutes = 10 * 60 * 1000;
private Timer timer = new Timer();
private boolean newTimer = false;
...
public void messageReceived() {
timer.cancel();
timer = new Timer();
newTimer = true;
TimerTask action = new TimerTask() {
public void run() {
if (!newTimer)
counter.increment();
else
newTimer = false;
}
};
timer.schedule(action, tenMinutes, tenMinutes);
}
...
我们的目标是设置一个计时器,该计时器仅在未收到新事件时才会触发操作。每次在十分钟过去之前调用 messageReceived() 时,应该取消计时器,这样它就不会触发。
我所看到的几乎每十分钟就会触发一次操作,即使每分钟调用 messageReceived 不止一次。
MessageReceived 是从服务调用的,因此它不会每次都在同一个线程上调用,但 messageReceived 在单例中。我不确定,但我认为如果多线程是问题所在,我会看到很多次“动作”触发,而不是每 10 分钟一次。
我认为你确实有一个 multi-threading 问题,就像 SnowmanXL 说的那样。这是一个简单的 MCVE 重现问题:
import java.text.SimpleDateFormat;
import java.util.*;
class MiscellaneousMonitor {
private static SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
private boolean isRunning;
private Counter counter;
private static final int tenMinutes = /*10 * 60 **/ 1000;
private Timer timer = new Timer();
private boolean newTimer = false;
static class Counter {
private int count = 0;
public /*synchronized*/ void increment() {
count++;
}
}
public /*synchronized*/ void start() {
counter = new Counter();
isRunning = true;
}
public /*synchronized*/ void messageReceived() {
timer.cancel();
timer = new Timer();
newTimer = true;
TimerTask action = new TimerTask() {
public void run() {
System.out.println(dateFormat.format(new Date()) + " Timer task running: " + this);
if (!newTimer)
counter.increment();
else
newTimer = false;
}
};
timer.schedule(action, tenMinutes, tenMinutes);
}
public /*synchronized*/ void stop() {
timer.cancel();
isRunning = false;
}
public /*synchronized*/ boolean isRunning() {
return isRunning;
}
public static void main(String[] args) throws InterruptedException {
MiscellaneousMonitor monitor = new MiscellaneousMonitor();
monitor.start();
Queue<Thread> threads = new LinkedList<>();
for (int t = 0; t < 10; t++) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try { Thread.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); }
monitor.messageReceived();
}
try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); }
});
thread.start();
threads.add(thread);
}
while (!threads.isEmpty()) {
threads.poll().join();
}
monitor.stop();
}
}
控制台日志将如下所示:
Exception in thread "Thread-4" java.lang.IllegalStateException: Timer already cancelled.
at java.base/java.util.Timer.sched(Timer.java:398)
at java.base/java.util.Timer.schedule(Timer.java:249)
at MiscellaneousMonitor.messageReceived(scratch_3.java:39)
at MiscellaneousMonitor.lambda$main[=11=](scratch_3.java:59)
at java.base/java.lang.Thread.run(Thread.java:832)
09:25:58.147 Timer task running: MiscellaneousMonitor@1ce7fd7d
09:25:58.142 Timer task running: MiscellaneousMonitor@7ba42a49
09:25:58.147 Timer task running: MiscellaneousMonitor@493cb0eb
09:25:58.147 Timer task running: MiscellaneousMonitor@6f9a3afe
09:25:58.148 Timer task running: MiscellaneousMonitor@1d86f308
Exception in thread "Thread-9" java.lang.IllegalStateException: Timer already cancelled.
at java.base/java.util.Timer.sched(Timer.java:398)
at java.base/java.util.Timer.schedule(Timer.java:249)
at MiscellaneousMonitor.messageReceived(scratch_3.java:39)
at MiscellaneousMonitor.lambda$main[=11=](scratch_3.java:59)
at java.base/java.lang.Thread.run(Thread.java:832)
09:25:58.445 Timer task running: MiscellaneousMonitor@53c65632
09:25:58.445 Timer task running: MiscellaneousMonitor@6ce24daa
09:25:58.445 Timer task running: MiscellaneousMonitor@784b861f
09:25:58.447 Timer task running: MiscellaneousMonitor@783528c9
09:25:58.447 Timer task running: MiscellaneousMonitor@2cc4944f
09:25:58.597 Timer task running: MiscellaneousMonitor@711e91d9
09:25:58.597 Timer task running: MiscellaneousMonitor@19ddcb88
09:25:58.597 Timer task running: MiscellaneousMonitor@5fbdc1a8
(...)
有时您会看到异常,有时不会,这取决于您 运行 程序的时机。但是即使你没有看到任何异常,多个定时器任务 - MiscellaneousMonitor
是匿名 TimerTask
实例的内部名称 - 将永远记录并且永远不会被取消,这就是程序继续 运行 永远,直到你杀死它,尽管你在所有 运行ning 任务上调用 join()
。但是还是有流氓TimerTask
现在,如果您取消注释我在代码中放置的所有 synchronized
关键字,您的控制台日志将更改为预期的
09:31:44.880 Timer task running: MiscellaneousMonitor@4f963263
程序将终止。
P.S.: 你也许可以在更小的代码部分而不是整个方法上同步,我没有分析过。我刚刚向您展示了线程不安全的基本问题,您的单例被多个其他线程访问,就像您说的那样。
我有以下代码,其目的是在定期调用停止到达 messageReceived() 时增加普罗米修斯计数器:
...
private static final int tenMinutes = 10 * 60 * 1000;
private Timer timer = new Timer();
private boolean newTimer = false;
...
public void messageReceived() {
timer.cancel();
timer = new Timer();
newTimer = true;
TimerTask action = new TimerTask() {
public void run() {
if (!newTimer)
counter.increment();
else
newTimer = false;
}
};
timer.schedule(action, tenMinutes, tenMinutes);
}
...
我们的目标是设置一个计时器,该计时器仅在未收到新事件时才会触发操作。每次在十分钟过去之前调用 messageReceived() 时,应该取消计时器,这样它就不会触发。
我所看到的几乎每十分钟就会触发一次操作,即使每分钟调用 messageReceived 不止一次。
MessageReceived 是从服务调用的,因此它不会每次都在同一个线程上调用,但 messageReceived 在单例中。我不确定,但我认为如果多线程是问题所在,我会看到很多次“动作”触发,而不是每 10 分钟一次。
我认为你确实有一个 multi-threading 问题,就像 SnowmanXL 说的那样。这是一个简单的 MCVE 重现问题:
import java.text.SimpleDateFormat;
import java.util.*;
class MiscellaneousMonitor {
private static SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
private boolean isRunning;
private Counter counter;
private static final int tenMinutes = /*10 * 60 **/ 1000;
private Timer timer = new Timer();
private boolean newTimer = false;
static class Counter {
private int count = 0;
public /*synchronized*/ void increment() {
count++;
}
}
public /*synchronized*/ void start() {
counter = new Counter();
isRunning = true;
}
public /*synchronized*/ void messageReceived() {
timer.cancel();
timer = new Timer();
newTimer = true;
TimerTask action = new TimerTask() {
public void run() {
System.out.println(dateFormat.format(new Date()) + " Timer task running: " + this);
if (!newTimer)
counter.increment();
else
newTimer = false;
}
};
timer.schedule(action, tenMinutes, tenMinutes);
}
public /*synchronized*/ void stop() {
timer.cancel();
isRunning = false;
}
public /*synchronized*/ boolean isRunning() {
return isRunning;
}
public static void main(String[] args) throws InterruptedException {
MiscellaneousMonitor monitor = new MiscellaneousMonitor();
monitor.start();
Queue<Thread> threads = new LinkedList<>();
for (int t = 0; t < 10; t++) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try { Thread.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); }
monitor.messageReceived();
}
try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); }
});
thread.start();
threads.add(thread);
}
while (!threads.isEmpty()) {
threads.poll().join();
}
monitor.stop();
}
}
控制台日志将如下所示:
Exception in thread "Thread-4" java.lang.IllegalStateException: Timer already cancelled.
at java.base/java.util.Timer.sched(Timer.java:398)
at java.base/java.util.Timer.schedule(Timer.java:249)
at MiscellaneousMonitor.messageReceived(scratch_3.java:39)
at MiscellaneousMonitor.lambda$main[=11=](scratch_3.java:59)
at java.base/java.lang.Thread.run(Thread.java:832)
09:25:58.147 Timer task running: MiscellaneousMonitor@1ce7fd7d
09:25:58.142 Timer task running: MiscellaneousMonitor@7ba42a49
09:25:58.147 Timer task running: MiscellaneousMonitor@493cb0eb
09:25:58.147 Timer task running: MiscellaneousMonitor@6f9a3afe
09:25:58.148 Timer task running: MiscellaneousMonitor@1d86f308
Exception in thread "Thread-9" java.lang.IllegalStateException: Timer already cancelled.
at java.base/java.util.Timer.sched(Timer.java:398)
at java.base/java.util.Timer.schedule(Timer.java:249)
at MiscellaneousMonitor.messageReceived(scratch_3.java:39)
at MiscellaneousMonitor.lambda$main[=11=](scratch_3.java:59)
at java.base/java.lang.Thread.run(Thread.java:832)
09:25:58.445 Timer task running: MiscellaneousMonitor@53c65632
09:25:58.445 Timer task running: MiscellaneousMonitor@6ce24daa
09:25:58.445 Timer task running: MiscellaneousMonitor@784b861f
09:25:58.447 Timer task running: MiscellaneousMonitor@783528c9
09:25:58.447 Timer task running: MiscellaneousMonitor@2cc4944f
09:25:58.597 Timer task running: MiscellaneousMonitor@711e91d9
09:25:58.597 Timer task running: MiscellaneousMonitor@19ddcb88
09:25:58.597 Timer task running: MiscellaneousMonitor@5fbdc1a8
(...)
有时您会看到异常,有时不会,这取决于您 运行 程序的时机。但是即使你没有看到任何异常,多个定时器任务 - MiscellaneousMonitor
是匿名 TimerTask
实例的内部名称 - 将永远记录并且永远不会被取消,这就是程序继续 运行 永远,直到你杀死它,尽管你在所有 运行ning 任务上调用 join()
。但是还是有流氓TimerTask
现在,如果您取消注释我在代码中放置的所有 synchronized
关键字,您的控制台日志将更改为预期的
09:31:44.880 Timer task running: MiscellaneousMonitor@4f963263
程序将终止。
P.S.: 你也许可以在更小的代码部分而不是整个方法上同步,我没有分析过。我刚刚向您展示了线程不安全的基本问题,您的单例被多个其他线程访问,就像您说的那样。