I think you do have a multi-threading problem, just like SnowmanXL said. Here is a simple MCVE reproducing the problem:
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 = 1000;
private Timer timer = new Timer();
private boolean newTimer = false;
static class Counter {
private int count = 0;
public void increment() {
count++;
}
}
public void start() {
counter = new Counter();
isRunning = true;
}
public 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 void stop() {
timer.cancel();
isRunning = false;
}
public 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();
}
}
The console log will look something like this:
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$0(scratch_3.java:59)
at java.base/java.lang.Thread.run(Thread.java:832)
09:25:58.147 Timer task running: MiscellaneousMonitor$1@1ce7fd7d
09:25:58.142 Timer task running: MiscellaneousMonitor$1@7ba42a49
09:25:58.147 Timer task running: MiscellaneousMonitor$1@493cb0eb
09:25:58.147 Timer task running: MiscellaneousMonitor$1@6f9a3afe
09:25:58.148 Timer task running: MiscellaneousMonitor$1@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$0(scratch_3.java:59)
at java.base/java.lang.Thread.run(Thread.java:832)
09:25:58.445 Timer task running: MiscellaneousMonitor$1@53c65632
09:25:58.445 Timer task running: MiscellaneousMonitor$1@6ce24daa
09:25:58.445 Timer task running: MiscellaneousMonitor$1@784b861f
09:25:58.447 Timer task running: MiscellaneousMonitor$1@783528c9
09:25:58.447 Timer task running: MiscellaneousMonitor$1@2cc4944f
09:25:58.597 Timer task running: MiscellaneousMonitor$1@711e91d9
09:25:58.597 Timer task running: MiscellaneousMonitor$1@19ddcb88
09:25:58.597 Timer task running: MiscellaneousMonitor$1@5fbdc1a8
(...)
Sometimes you see the execeptions, sometimes not, depending on the timing when you run the program. But even if you do not see any exceptions, multiple timer tasks - MiscellaneousMonitor$1
is the internal name of the anonymous TimerTask
instance - will log forever and never be cancelled, which is why the program continues to run forever until you kill it, despite you calling join()
on all running tasks. But there are still rogue TimerTask
s.
Now if you uncomment all synchronized
keywords where I put them in the code, your console log will change to the expected
09:31:44.880 Timer task running: MiscellaneousMonitor$1@4f963263
and the program will terminate.
P.S.: You maybe could synchronise on smaller sections of code instead of on whole methods, I did not analyse that. I just showed you the basic problem of thread unsafety with your singleton which is accessed by multiple other threads, like you said.