2010-01-20 14 views
34

Sto lavorando a un progetto Java in cui è necessario avere più attività in esecuzione in modo asincrono. Sono portato a credere che l'Executor sia il modo migliore per me di farlo, quindi mi sto familiarizzando con esso. (Si è pagato per imparare!) Tuttavia, non mi è chiaro quale sia il modo migliore per realizzare ciò che sto cercando di fare.Procedure consigliate di Java Executor per attività che devono essere eseguite per sempre

Per ragioni, diciamo che ho due attività in esecuzione. Nessuno dei due si prevede che termini, ed entrambi dovrebbero essere eseguiti per tutta la durata della vita dell'applicazione. Sto cercando di scrivere una classe wrapper principale tale che:

  • Se una delle attività genera un'eccezione, il wrapper la cattura e riavvia l'attività.
  • Se una delle attività viene completata, il wrapper noterà e riavvierà l'attività.

Ora, si deve rilevare che l'implementazione per entrambe le attività si concluderà il codice in run() in un ciclo infinito che non verrà mai eseguito fino al completamento, con un blocco try/catch che dovrebbe gestire tutte le eccezioni di runtime senza interrompere il ciclo continuo. Sto cercando di aggiungere un altro livello di certezza; se io o qualcuno che mi segue fa qualcosa di stupido che sconfigge queste misure di sicurezza e interrompe il compito, l'applicazione deve reagire in modo appropriato.

C'è una buona pratica per affrontare questo problema che la gente più esperta di me consiglierebbe?

FWIW, ho sbattuto-up questa classe di test:


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) {} 
     } 
    } 
} 

E 'comportarsi come voglio, ma non so se ci sono trucchi, inefficienze, o addirittura male di testa di attesa per sorprendermi. (In realtà, dato che io sono nuovo a questo, sarei scioccato se non ci fosse qualcosa che non andava/sconsigliabile su di esso).

Tutta la comprensione è accolto favorevolmente.

+0

Se si esegue ciascuno di questi due compiti che il suo esecutore a thread singolo, si può semplicemente mettere 1000 copie del compito in l'esecutore. :-) –

+0

Quindi, se uno dei due compiti fallisce, 999 attendono di prendere il suo posto? Eh. Carino, ma credimi, ho davvero bisogno che questo pollone sia a prova di proiettile come posso farcela. Da qui questa richiesta essenzialmente per una revisione del codice della comunità. – BlairHippo

+2

Ok, sul tema della prova a prova di proiettile: qual è il tuo piano attuale per affrontare le attività che si trovano in deadlock o altri fallimenti di liveness? Solo perché puoi ricominciare più e più volte non significa che continuerà a funzionare, se questi problemi li possono mantenere indefinitamente. –

risposta

30

ho affrontato una situazione simile nel mio progetto precedente, e dopo il mio codice soffiava a fronte di un cliente arrabbiato, i miei amici e ho aggiunto due grandi safe-guardie:

  1. Nel ciclo infinito, cattura Anche errori, non solo eccezioni. A volte accadono cose non consentite e Java genera un Errore, non un'eccezione.
  2. Utilizzare un interruttore di back-off, quindi se qualcosa va storto e non è recuperabile, non si aumenta la situazione avviando avidamente un altro ciclo.Invece, è necessario attendere fino a quando la situazione torna alla normalità e quindi ricominciare.

Ad esempio, abbiamo avuto una situazione in cui il database è andato giù e durante il ciclo è stata lanciata una SQLException. Lo sfortunato risultato è stato che il codice ha ripreso il ciclo, solo per colpire di nuovo la stessa eccezione, e così via. I log hanno mostrato che abbiamo raggiunto la stessa SQLException circa 300 volte in un secondo !! ... questo è accaduto a intermittenza diverse volte con pause JVM occasionali di 5 secondi circa, durante le quali l'applicazione non era reattiva, fino a quando non è stato lanciato un errore e il thread è morto!

Così abbiamo implementato una strategia di back-off, approssimativamente mostrata nel codice sottostante, che se l'eccezione non è recuperabile (o è previsto il recupero entro pochi minuti), quindi aspettiamo un tempo più lungo prima di riprendere le operazioni .

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) { 
     } 
    } 
    } 
} 

se si implementa le attività in questo modo, allora non vedo un senso avere un terzo filo "watch-dog" con le checkTasks (metodo). Inoltre, per le stesse ragioni che ho illustrato sopra, sarei cauto nel riprendere l'attività con l'esecutore. Per prima cosa è necessario capire perché l'attività ha avuto esito negativo e se l'ambiente si trova in una condizione stabile per cui sarebbe utile eseguire nuovamente l'attività.

+1

Grazie per entrambe le misure di sicurezza, li terrò a mente entrambi. L'interruttore di back-off è particolarmente sensibile; Lo considero la salvaguardia di livello più basso, se non altro che fallisce. Cercare di essere sensibile alle condizioni che hanno causato il fallimento andrà altrove; questo è semplice "Qualcosa spegne? GYAH! MUNGO TORNIA SU INDIETRO !!!" logica. – BlairHippo

7

A parte il bulbo oculare, generalmente eseguo codice Java contro strumenti di analisi statici come PMD e FindBugs per cercare problemi più profondi.

In particolare per questo codice, ai FindBug non piaceva che risultati1 e risultati2 non siano volatili nell'iniziale lazy e che i metodi run() possano ignorare l'eccezione perché non vengono gestiti in modo esplicito.

In generale sono un po 'sospettoso sull'uso di Thread.sleep per i test di concorrenza, preferendo i timer o terminando stati/condizioni. Callable potrebbe essere utile nel restituire qualcosa in caso di un'interruzione che genera un'eccezione se non è in grado di calcolare un risultato.

Per alcune buone pratiche e più spunti di riflessione, controlla Concurrency in Practice.

+1

Grazie per i suggerimenti, in particolare PMD e FindBugs; Non avevo familiarità con quegli strumenti. Lo sono ora. :-) – BlairHippo

0

hai provato Quartz framework?

+0

No, ma dando un'occhiata veloce a quella pagina, Quartz sembra eccessivo. Devo solo eseguire due (forse tre) attività e assicurarmi che siano SEMPRE IN CORSO. Quali sono i vantaggi di Quartz che mi dà una soluzione più semplice? – BlairHippo

+0

sì, il quarzo potrebbe essere un po 'eccessivo per un semplice problema. Lo raccomanderei più specificamente per l'elaborazione in batch. –

0

come su questo

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); 
Problemi correlati