2010-01-26 14 views
5

(similar a "Resettable Java Timer" pero hay algunas sutilezas que necesito para explorar)tiempo de espera de puesta a cero en Java

necesito una característica de tiempo de espera reajustable, de modo que si mi clase no realiza una acción particular dentro de un intervalo de tiempo T0 (donde T0 es en el barrio de 50-1000msec), a continuación, un método es llamado:

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(); } 
} 

¿Qué puedo usar para implementar esto? Estoy familiarizado con ScheduledExecutorService, y la idea de llamar a ScheduledFuture.cancel() y reprogramar la tarea parece como debería funcionar, pero luego hay un peligro potencial si cancela() falla y la tarea programada se ejecuta cuando no debería. Siento que me falta una sutileza aquí.

También (quizás más importante), ¿hay alguna manera de probar mi implementación/probar que funciona correctamente?

edición: Estoy particularmente preocupado por el caso en el que se llama a menudo criticalMethod() (quizás varias veces por milisegundo) ... si uso ScheduledExecutorService, apenas se parece como un problema de recursos potencial para seguir creando nuevas tareas programadas + cancelando los viejos, en lugar de tener una manera directa de reprogramar una tarea.

+0

Como una adición final a mi respuesta, definitivamente deberías leer Java Concurrency in Practice si no lo has hecho aún. Todas las ideas en mi respuesta provienen de ese libro (o experiencia personal, pero el libro coincide con mis experiencias). –

Respuesta

3

El atributo cancelado se adjunta al objeto de la tarea. Por lo tanto, o bien la tarea no se inició cuando llamas al cancel, y no se ejecutará; o la tarea ya comenzó cuando llamas al cancel, y se interrumpe.

Cómo manejar la interrupción depende de usted. Debería sondear regularmente Thread.interrupted() (que, dicho sea de paso, restablece el indicador interrumpido, así que tenga cuidado) si no llama a ninguna función interrumpible (las que declaran InterruptedException en su cláusula throws).

Por supuesto, si llama a tales funciones, debe manejar InterruptedException con sensatez (lo que incluye reafirmar el indicador interrumpido (Thread.currentThread().interrupt()) antes de que su tarea regrese). :-)

Para responder a su edición, la creación de objetos es barata, siempre y cuando su objeto no tenga mucho estado. Personalmente, no me preocuparía demasiado a menos que los perfiles muestren que es un cuello de botella.

+0

atributo cancelado se adjunta al objeto de la tarea .... ¿y si programo el mismo Runnable dos veces? ¿no se mantendría el atributo cancelado como estado con el valor ScheduledFuture que resulta de la programación de la tarea? –

+0

Cada vez que programe un 'Runnable', se creará una nueva' ScheduledFutureTask'. –

+0

ah, vale, eso tiene sentido. –

7

bien, aquí hay un intento de utilizar ScheduledExecutorService. Estoy impresionado por el rendimiento; Ejecuté el programa de prueba con argumentos 50 1 10 (50 mseg de tiempo de espera; cada 1 mseg el ResettableTimer se reinicia 10 veces) y no usa prácticamente ninguna de mi 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!"); 
      } 
     } 
    } 
} 
Cuestiones relacionadas