带复位的定时器

时间:2017-02-28 23:19:28

标签: java

是否有一种有效的方法在Java中使用重置功能实现倒数计时器?

我正在将事件发送到服务器。如果一段时间没有事件发生,我需要发送一个心跳信号,以便服务器知道我们还活着。事件的发送频率必须低于每200毫秒。

理想情况下,我有一个倒数计时器,我设置为200毫秒。每次发生事件时,我都会将计时器重置为200毫秒。如果计时器超时,它将发送心跳并自行重置。

我不想为每个计时器绑定一个线程,因为我可能需要其中几个。我不想使用Timer + TimerTask,因为每次重置都需要创建一个新的TimerTask并将其添加到队列中。在重负载下,我可能每秒要发射几千个事件,并且不想为每个事件创建一个TimerTask。

我提出的最佳方案是使用Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(...);并每10或20毫秒启动一次。我可以检查每个滴答的倒计时值并根据需要重置它。但这并不是非常有效,并且不会给我一个好的计时器分辨率。

有更好的方法吗?

2 个答案:

答案 0 :(得分:2)

您可以使用Object.wait(long)Object.notify()滚动自己的自定义计时器。

public abstract class ResettableTimer implements Runnable {

    private Object lock = new Object();
    private long timeout_ms;
    private long last;

    public ResettableTimer(long timeout_ms) {
        this.timeout_ms = timeout_ms;
    }

    public void reset() {
        synchronized (lock) {
            last = System.currentTimeMillis();
            lock.notify();
        }
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                for (;;) {
                    lock.wait(timeout_ms);
                    long delta = System.currentTimeMillis() - last;
                    if (delta >= timeout_ms) {
                        timeout();
                    }
                }
            }
        } catch (InterruptedException e) {
        }
    }

    protected abstract void timeout();
}

创建一个子类,覆盖timeout()以发送心跳消息。每次发送消息时,请调用ResettableTimer#reset()方法重置心跳计时器。

希望有所帮助。

答案 1 :(得分:1)

使用ExecutorService是有道理的。 Goetz等人认为它比 Java Concurrency in Practice 中的util.Timer有所改进。

我会考虑以下内容:

  • 有一个volatile变量,用于保存最后一个事件的 lastEventTime
  • 设置在 lastEventTime + 200 ms执行的非重复TimerTask

当TimerTask发生时,如果 lastEventTime 是>过去200毫秒,发送ping并将 lastEventTime 变量更新到当前,然后创建并设置一个新的TimerTask以再次检查新的 lastEventTime + 200(其中将来将是200毫秒)。

如果在TimerTask检入时,过去发生的事件少于200毫秒(例如过去150毫秒),请将下一个TimerTask设置为在当前 lastEventTime +处再次检入200(未来50毫秒,在示例中)。