Java中的resettable超时

Jas*_*n S 5 java timeout timer

(类似于"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(); }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Jas*_*n S 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!");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Chr*_*ung 3

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

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

当然,如果您正在调用此类函数,您应该InterruptedException明智地处理(包括Thread.currentThread().interrupt()在任务返回之前重新声明中断标志())。:-)

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