(类似于"Resettable Java Timer",但我需要探索一些细微之处)
我需要一个可重置的超时功能,这样如果我的类在时间间隔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()失败则存在潜在的危险并且计划任务在不应该执行时执行。我觉得我在这里错过了一个微妙的东西。
另外(也许更重要的是),有没有办法测试我的实现/证明它正常工作?
编辑:我特别关注criticalMethod()
经常被调用的情况(可能每毫秒几次)...如果我使用ScheduledExecutorService,它看起来像是一个潜在的资源问题是继续创建新的计划任务+取消旧任务,而不是直接重新安排任务。
答案 0 :(得分:7)
好的,这是尝试使用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<ScheduledFuture<?>> ticket
= new AtomicReference<ScheduledFuture<?>>();
/* 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!");
}
}
}
}
答案 1 :(得分:3)
取消的属性附加到任务对象。因此,当您致电cancel
时,任务尚未开始,并且无法运行;或者当您致电cancel
时,任务已经开始,并且它会被中断。
如何处理中断取决于您。如果你没有调用任何可中断的函数(那些在Thread.interrupted()
子句中声明InterruptedException
的函数,你应该定期轮询throws
(顺便说一下,重置中断的标志,所以要小心) )。
当然,如果您正在调用此类函数,则应该明智地处理InterruptedException
(包括在任务返回之前重新声明中断的标志(Thread.currentThread().interrupt()
))。 : - )
要回答您的编辑,只要您的对象没有很多状态,对象创建就很便宜。除非剖析显示它是一个瓶颈,否则我个人不会太担心它。