簡體   English   中英

Java Timer/TimerTask - 如果未收到消息則觸發事件

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

我有以下代碼,其目的是在定期調用停止進入 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);

    }

  ...

目標是設置一個計時器,如果沒有收到新事件,它只會觸發一個動作。 每次在十分鍾過去之前調用 messageReceived() 時,都應該取消計時器,這樣它就不會觸發。

我所看到的幾乎是每十分鍾觸發一次動作,即使 messageReceived 每分鍾被調用一次以上。

MessageReceived 是從服務中調用的,因此它不會每次都在同一個線程上調用,但 messageReceived 位於單例中。 我不確定,但我認為如果多線程是問題所在,我會看到許多“動作”觸發,而不僅僅是每 10 分鍾觸發一次。

我認為您確實存在多線程問題,就像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$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
(...)

有時您會看到異常,有時不會,這取決於您運行程序的時間。 但即使您沒有看到任何異常,多個計時器任務 - MiscellaneousMonitor$1是匿名TimerTask實例的內部名稱 - 將永遠記錄並且永遠不會被取消,這就是為什么該程序將永遠運行直到您殺死它,盡管您在所有正在運行的任務上調用join() 但是仍然有流氓TimerTask s。

現在,如果您取消注釋我將它們放在代碼中的所有synchronized關鍵字,您的控制台日志將更改為預期

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

並且程序將終止。

PS:您也許可以在較小的代碼部分而不是整個方法上進行同步,我沒有分析。 我剛剛向您展示了由多個其他線程訪問的單例線程不安全的基本問題,就像您說的那樣。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM