(подобный "Восстановленному Таймеру Java", но существует некоторая тонкость, которую я должен исследовать),
Мне нужна восстановленная функция тайм-аута, так, чтобы, если мой класс не выполняет конкретное действие в течение интервала времени T0 (где T0 находится в окружении 50-1000msec), затем метод назвали:
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 () и затем перепланирование, задача кажется, что должна работать, но затем существует потенциальная опасность, если отмена () сбои и запланированная задача выполняются, когда она не была должна. Я чувствую, что пропускаю тонкость здесь.
Также (возможно, что еще более важно), существует ли способ протестировать мою реализацию / доказывают, что это работает правильно?
править: Я особенно обеспокоен случаем где criticalMethod()
называется часто (возможно, несколько раз на миллисекунду)..., если я использую ScheduledExecutorService, на потенциальную проблему ресурса просто походит продолжать создавать новые запланированные задачи + отмена старых, вместо того, чтобы иметь прямой способ перенести задачу.
Отмененный атрибут прикрепляется к объекту задачи. Таким образом, либо задача не запущена при вызове cancel
, и она не будет запущена; либо задача уже запущена при вызове cancel
, и она прерывается.
Как обработать прерывание зависит от вас. Вам следует регулярно опрашивать Thread.interrupted()
(который, кстати, сбрасывает флаг прерывания, так что будьте осторожны), если вы не вызываете прерывную функцию (те, которые в своем throwws
объявляют InterruptedException
).
Конечно, если вы вызываете такие функции, то вы должны обрабатывать InterruptedException
разумно (что включает в себя установку флага прерывания (Thread.currentThread().interrupt()
) до того, как ваша задача вернётся). :-)
Для ответа на ваше редактирование, создание объектов дешево, пока ваш объект не имеет большого состояния. Лично я бы не стал слишком беспокоиться об этом, если бы профилирование не показало, что это узкое место.
ок, вот попытка использования ScheduledExecutorService. Я впечатлен производительностью; я запустил тестовую программу с аргументами 50 1 10 (тайм-аут 50 мс; каждые 1 мс ResettableTimer сбрасывается 10 раз), и она практически не использует ни один из моих процессоров.
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!");
}
}
}
}