2010-01-20 10 views
34

Estoy trabajando en un proyecto Java donde necesito tener varias tareas ejecutándose de forma asíncrona. Me hacen creer que el ejecutor es la mejor manera de hacerlo, así que me estoy familiarizando con eso. (¡Sí, se les paga para aprender!) Sin embargo, no está claro para mí cuál es la mejor manera de lograr lo que intento hacer.Mejores prácticas de Java Executor para tareas que deberían ejecutarse Forever

Por el bien del argumento, digamos que tengo dos tareas en ejecución. No se espera que ninguno de los dos finalice y ambos deberían ejecutarse durante la vida de la aplicación. Estoy tratando de escribir una clase contenedora principal tal que:

  • Si alguna tarea arroja una excepción, la envoltura la detectará y reiniciará la tarea.
  • Si alguna de las tareas se ejecuta hasta su finalización, el contenedor notará y reiniciará la tarea.

Ahora, hay que señalar que la aplicación de ambas tareas se envolverá el código en run() en un bucle infinito que nunca se quedará hasta su finalización, con un bloque try/catch que debe manejar todas las excepciones de tiempo de ejecución sin interrumpir el lazo. Estoy tratando de agregar otra capa de certeza; si yo o alguien que me sigue hace algo estúpido que derrota estas salvaguardas y se detiene la tarea, la aplicación debe reaccionar de manera adecuada.

¿Existe una mejor práctica para abordar este problema que la gente más experimentada que yo recomendaría?

Fwiw, he azotado en marcha esta clase de prueba:


public class ExecTest { 

    private static ExecutorService executor = null; 
    private static Future results1 = null; 
    private static Future results2 = null; 

    public static void main(String[] args) { 
     executor = Executors.newFixedThreadPool(2); 
     while(true) { 
     try { 
      checkTasks(); 
      Thread.sleep(1000); 
     } 
     catch (Exception e) { 
      System.err.println("Caught exception: " + e.getMessage()); 
     } 
     } 
    } 

    private static void checkTasks() throws Exception{ 
     if (results1 == null || results1.isDone() || results1.isCancelled()) { 
     results1 = executor.submit(new Test1()); 
     } 

     if (results2 == null || results2.isDone() || results2.isCancelled()) { 
     results2 = executor.submit(new Test2()); 
     } 
    } 
} 

class Test1 implements Runnable { 
    public void run() { 
     while(true) { 
     System.out.println("I'm test class 1"); 
     try {Thread.sleep(1000);} catch (Exception e) {} 
     } 

    } 
} 

class Test2 implements Runnable { 
    public void run() { 
     while(true) { 
     System.out.println("I'm test class 2"); 
     try {Thread.sleep(1000);} catch (Exception e) {} 
     } 
    } 
} 

Es comportarse de la manera que quiero, pero no sé si hay trampas, ineficiencias, o francamente mala cabeza de espera para sorprenderme (De hecho, dado que soy nuevo en esto, me sorprendería si no fuera algo malo/desaconsejable al respecto.)

Cualquier idea es bienvenida.

+0

Si ejecuta cada una de esas dos tareas que su propio ejecutor de un solo subproceso, sólo puede poner 1000 copias de la tarea en el ejecutor. :-) –

+0

Entonces, si cualquiera de las tareas falla, ¿999 están esperando para tomar su lugar? Je. Lindo, pero créanme, realmente necesito que este tonto sea tan resistente a las balas como pueda. De ahí esta solicitud de esencialmente una revisión del código de la comunidad. – BlairHippo

+2

Bien, sobre el tema de a prueba de balas: ¿cuál es su plan actual para lidiar con tareas que entran en un punto muerto u otras fallas en la vida? El hecho de que pueda reiniciarse una y otra vez no significa que seguirá funcionando, si esos problemas pueden retrasarlos indefinidamente. –

Respuesta

30

que se enfrentó a una situación similar en mi proyecto anterior, y después de mi código sopló en la cara de un cliente enojado, mis amigos y yo añadieron dos grandes seguras guardias:

  1. En el bucle infinito, la captura También errores, no solo excepciones. A veces suceden cosas no contempladas y Java le lanza un Error, no una Excepción.
  2. Use un interruptor de retroceso, por lo que si algo sale mal y no es recuperable, no intensificará la situación al comenzar otro ciclo con entusiasmo.En cambio, debe esperar hasta que la situación vuelva a la normalidad y luego comenzar de nuevo.

Por ejemplo, tuvimos una situación en la que la base de datos bajó y durante el ciclo se lanzó una excepción SQLException. El desafortunado resultado fue que el código pasó nuevamente por el ciclo, solo para volver a la misma excepción, y así sucesivamente. Los registros mostraron que alcanzamos la misma SQLException unas 300 veces en un segundo. ... esto sucedió intermitentemente varias veces con pausas JVM ocasionales de 5 segundos más o menos, durante las cuales la aplicación no respondió, hasta que finalmente se arrojó un error y el hilo murió.

Así que implementamos una estrategia de retroceso, aproximadamente mostrada en el código siguiente, que si la excepción no es recuperable (o se puede recuperar en cuestión de minutos), esperamos un tiempo más prolongado antes de reanudar las operaciones .

class Test1 implements Runnable { 
    public void run() { 
    boolean backoff = false; 
    while(true) { 
     if (backoff) { 
     Thread.sleep (TIME_FOR_LONGER_BREAK); 
     backoff = false; 
     } 
     System.out.println("I'm test class 1"); 
     try { 
     // do important stuff here, use database and other critical resources 
     } 
     catch (SqlException se) { 
     // code to delay the next loop 
     backoff = true; 
     } 
     catch (Exception e) { 
     } 
     catch (Throwable t) { 
     } 
    } 
    } 
} 

Si implementa las tareas de esta manera entonces yo no veo una razón para tener un tercer hilo "perro guardián" con los checkTasks (método). Además, por las mismas razones que expliqué anteriormente, sería cauteloso para comenzar nuevamente la tarea con el ejecutor. En primer lugar, debe comprender por qué falló la tarea y si el entorno se encuentra en una condición estable que la ejecución de la tarea nuevamente sería útil.

+1

Gracias por ambas precauciones, los tendré en cuenta. El interruptor de retroceso es particularmente sensible; Estoy tratando esto como la salvaguarda de nivel más bajo, si es que falla todo lo demás. Tratar de ser sensato acerca de las condiciones que causaron el fracaso se irá a otro lado; esto es simple "¿Algo se apaga? ¡GYAH! ¡MUNGO LO VUELVE A ENCENDER!" lógica. – BlairHippo

7

Además de echarle un vistazo, generalmente ejecuto código Java contra herramientas de análisis estático como PMD y FindBugs para buscar problemas más profundos.

Específicamente para este código, a los FindBugs no les gustó que los resultados1 y los resultados2 no son volátiles en el init diferido, y que los métodos run() pueden ignorar la Excepción porque no se manejan explícitamente.

En general, estoy un poco receloso del uso de Thread.sleep para las pruebas de simultaneidad, prefiriendo temporizadores o estados/condiciones de terminación. Invocar puede ser útil para devolver algo en caso de una interrupción que arroje una excepción si no se puede calcular un resultado.

Para conocer algunas de las mejores prácticas y más elementos de reflexión, consulte Concurrency in Practice.

+1

Gracias por los consejos, particularmente PMD y FindBugs; No estaba familiarizado con esas herramientas. Soy ahora. :-) – BlairHippo

0

¿Has probado Quartz framework?

+0

No, pero echando un vistazo rápido a esa página, Quartz parece excesivo. Solo necesito ejecutar dos (tal vez tres) tareas y asegurarme de que SIEMPRE FUNCIONen. ¿Qué ventajas me está dando Quartz que una solución más simple no tiene? – BlairHippo

+0

Sí, Quartz podría ser un poco exagerado por un problema simple. Lo recomendaría más específicamente para el procesamiento por lotes. –

0

¿qué tal esto

Runnable task =() -> { 
    try{ 
    // do the task steps here 
    } catch (Exception e){ 
    Thread.sleep (TIME_FOR_LONGER_BREAK); 
    }  
}; 
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); 
executor.scheduleAtFixedRate(task,0, 0,TimeUnit.SECONDS);