Java中的可重置超时

Java中的可重置超时,java,timeout,timer,Java,Timeout,Timer,(与类似,但有一些微妙之处需要探索) 我需要一个可重置超时功能,这样,如果我的类在时间间隔T0(其中T0在50-1000毫秒附近)内没有执行特定操作,则会调用一个方法: class MyClass { static final private timeoutTime = 50; final private SomeTimer timer = new SomeTimer(timeoutTime, new Runnable () { public void run(

(与类似,但有一些微妙之处需要探索)

我需要一个可重置超时功能,这样,如果我的类在时间间隔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(); }
}
我可以用什么来实现这一点?我很熟悉,调用然后重新安排任务的想法似乎应该可以奏效,但是如果cancel()失败,并且计划的任务在不应该的时候执行,那么就会有潜在的危险。我觉得我错过了一个微妙之处

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


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

取消属性附加到任务对象。因此,当您调用
cancel
时,任务没有启动,并且它不会运行;或者,当您调用
cancel
时,任务已经启动,并且任务被中断

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

当然,如果您正在调用此类函数,您应该明智地处理
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<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!");
            }
        }
    }
}
package com.example.test;
导入java.util.concurrent.ScheduledExecutorService;
导入java.util.concurrent.ScheduledFuture;
导入java.util.concurrent.ScheduledThreadPoolExecutor;
导入java.util.concurrent.TimeUnit;
导入java.util.concurrent.AtomicReference;
公共类重置表计时器{
最终私有调度ecutorservice调度程序;
最终私有长超时;
最终专用时间单位;
最终私有可运行任务;
最终私有原子参考>();
/*使用原子引用管理并发性
*在从不同线程调用reset()的情况下
*/
公共ResettableTimer(ScheduledExecutorService调度程序,
长超时、时间单位(时间单位、可运行任务)
{
this.scheduler=调度程序;
this.timeout=超时;
this.timeUnit=时间单位;
this.task=任务;
}
公共ResettableTimer重置(布尔值可中断刷新){
/*
*新进旧出;
*这可能意味着在短时间内一次调度多个任务,
*但这并不是什么大问题,避免了代码中的一些复杂性
*/
ScheduledFuture newTicket=this.scheduler.schedule(
this.task,this.timeout,this.timeUnit);
ScheduledFuture oldTicket=this.ticket.getAndSet(newTicket);
如果(旧票证!=null)
{
旧票。取消(可能会中断);
}
归还这个;
}
静态公共void main(字符串[]args)
{
如果(参数长度>=3)
{
int timeout=Integer.parseInt(args[0]);
int period=Integer.parseInt(args[1]);
final int nresetsPerPeriod=Integer.parseInt(args[2]);
ScheduledExecutorService调度程序=新的ScheduledThreadPoolExecutor(1);
最终ResettableTimer计时器=新的ResettableTimer(调度程序,
超时,TimeUnit.ms,
新的Runnable(){
public void run(){System.out.println(“超时!”;}
}
);
//启动一个单独的线程池进行重置
新的ScheduledThreadPoolExecutor(5).scheduleAtFixedRate(新的Runnable(){
私有int运行计数器=0;
public void run(){
对于(int i=0;i
作为我答案的最后一个补充,如果您还没有阅读过Java并发性,那么您肯定应该在实践中仔细阅读。我答案中的所有想法都来自那本书(或个人经验,但这本书与我的经验相匹配)。取消属性附加到任务对象。。。。如果我安排两次相同的Runnable怎么办?取消属性是否会保留为调度任务所产生的ScheduledFuture的状态?每次调度
Runnable
,都会为其创建一个新的
ScheduledFutureTask