Java中的resettable超时

(类似于“Resettable Java Timer”,但我需要探索一些细微之处)

我需要一个可重置的超时function,这样如果我的类在时间间隔T0内没有执行特定动作(其中T0在50-1000毫秒附近),则会调用一个方法:

class MyClass { static final private timeoutTime = 50; final private SomeTimer timer = new SomeTimer(timeoutTime, new Runnable () { public void run() { onTimeout(); }}); private void onTimeout() { /* do something on timeout */ } public void criticalMethod() { this.timer.reset(); } } 

我可以用什么来实现这个? 我熟悉ScheduledExecutorService ,并且调用ScheduledFuture.cancel()然后重新安排任务的想法似乎应该可以工作,但是如果cancel()失败并且计划任务在不应该执行时执行则存在潜在危险。 我觉得我在这里错过了一个微妙的东西。

另外(也许更重要的是),有没有办法测试我的实现/certificate它正常工作?

编辑:我特别关注的是经常调用criticalMethod()的情况(可能每毫秒几次)…如果我使用ScheduledExecutorService,那么继续创建新的计划任务+取消旧任务似乎是一个潜在的资源问题,而不是直接重新安排任务。

取消的属性附加到任务对象。 因此,当您调用cancel ,任务尚未启动,并且它将无法运行; 或者当您调用cancel时任务已经开始,并且它被中断。

如何处理中断取决于你。 你应该定期轮询Thread.interrupted() (顺便说一下,重置中断的标志,所以要小心)如果你没有调用任何可中断的函数(那些在throws子句中声明InterruptedException函数)。

当然,如果要调用这些函数,则应该明智地处理InterruptedException (包括在任务返回之前重新声明中断标志( Thread.currentThread().interrupt() )。 🙂

要回答您的编辑,只要您的对象没有很多状态,对象创建就很便宜。 我个人不会太担心它,除非分析显示它是一个瓶颈。

好的,这是尝试使用ScheduledExecutorService。 表演让我印象深刻; 我使用参数50 1 10(50毫秒超时;每1毫秒ResettableTimer重置10次)运行测试程序,它几乎不使用我的CPU。

 package com.example.test; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class ResettableTimer { final private ScheduledExecutorService scheduler; final private long timeout; final private TimeUnit timeUnit; final private Runnable task; final private AtomicReference> ticket = new AtomicReference>(); /* use AtomicReference to manage concurrency * in case reset() gets called from different threads */ public ResettableTimer(ScheduledExecutorService scheduler, long timeout, TimeUnit timeUnit, Runnable task) { this.scheduler = scheduler; this.timeout = timeout; this.timeUnit = timeUnit; this.task = task; } public ResettableTimer reset(boolean mayInterruptIfRunning) { /* * in with the new, out with the old; * this may mean that more than 1 task is scheduled at once for a short time, * but that's not a big deal and avoids some complexity in this code */ ScheduledFuture newTicket = this.scheduler.schedule( this.task, this.timeout, this.timeUnit); ScheduledFuture oldTicket = this.ticket.getAndSet(newTicket); if (oldTicket != null) { oldTicket.cancel(mayInterruptIfRunning); } return this; } static public void main(String[] args) { if (args.length >= 3) { int timeout = Integer.parseInt(args[0]); int period = Integer.parseInt(args[1]); final int nresetsPerPeriod = Integer.parseInt(args[2]); ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1); final ResettableTimer timer = new ResettableTimer(scheduler, timeout, TimeUnit.MILLISECONDS, new Runnable() { public void run() { System.out.println("timeout!"); } } ); // start a separate thread pool for resetting new ScheduledThreadPoolExecutor(5).scheduleAtFixedRate(new Runnable() { private int runCounter = 0; public void run() { for (int i = 0; i < nresetsPerPeriod; ++i) { timer.reset(false); } if ((++this.runCounter % 100) == 0) { System.out.println("runCounter: "+this.runCounter); } } }, 0, period, TimeUnit.MILLISECONDS); try { while (true) { Thread.sleep(1000); } } catch (InterruptedException e) { System.out.println("interrupted!"); } } } }