简体   繁体   English

Java Timer/TimerTask - 如果未收到消息则触发事件

[英]Java Timer/TimerTask - fire event if message not received

I have the following code whose purpose is to increment a prometheus counter if periodic calls stop coming to messageReceived():我有以下代码,其目的是在定期调用停止进入 messageReceived() 时增加 prometheus 计数器:

...

    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);

    }

  ...

The goal is to set a timer that will only fire an action if a new event is not received.目标是设置一个计时器,如果没有收到新事件,它只会触发一个动作。 Every time messageReceived() is called before ten minutes have passed, the timer should be cancelled so it will not fire.每次在十分钟过去之前调用 messageReceived() 时,都应该取消计时器,这样它就不会触发。

What I am seeing happen is pretty much exactly every ten minutes the action fires, even though messageReceived is called more than once per minute.我所看到的几乎是每十分钟触发一次动作,即使 messageReceived 每分钟被调用一次以上。

MessageReceived is called from a service so its not called on the same thread every time, but messageReceived is inside a singleton. MessageReceived 是从服务中调用的,因此它不会每次都在同一个线程上调用,但 messageReceived 位于单例中。 I am not sure, but I would think that if multithreading was the problem, I would see many firings of "action" and not just one every 10 minutes.我不确定,但我认为如果多线程是问题所在,我会看到许多“动作”触发,而不仅仅是每 10 分钟触发一次。

I think you do have a multi-threading problem, just like SnowmanXL said.我认为您确实存在多线程问题,就像SnowmanXL所说的那样。 Here is a simple MCVE reproducing the problem:这是一个简单的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();
  }
}

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.但即使您没有看到任何异常,多个计时器任务 - MiscellaneousMonitor$1是匿名TimerTask实例的内部名称 - 将永远记录并且永远不会被取消,这就是为什么该程序将永远运行直到您杀死它,尽管您在所有正在运行的任务上调用join() But there are still rogue TimerTask s.但是仍然有流氓TimerTask s。

Now if you uncomment all synchronized keywords where I put them in the code, your console log will change to the expected现在,如果您取消注释我将它们放在代码中的所有synchronized关键字,您的控制台日志将更改为预期

09:31:44.880 Timer task running: MiscellaneousMonitor$1@4f963263

and the program will terminate.并且程序将终止。

PS: You maybe could synchronise on smaller sections of code instead of on whole methods, I did not analyse that. PS:您也许可以在较小的代码部分而不是整个方法上进行同步,我没有分析。 I just showed you the basic problem of thread unsafety with your singleton which is accessed by multiple other threads, like you said.我刚刚向您展示了由多个其他线程访问的单例线程不安全的基本问题,就像您说的那样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM