Porting del codice dall'uso dei timer per programmare il servizio di esecuzione
-
10-07-2019 - |
Domanda
Sto provando a trasferire il codice dall'uso di java timer all'utilizzo di scheduledexecutorservice
Ho il seguente caso d'uso
class A {
public boolean execute() {
try {
Timer t = new Timer();
t.schedule (new ATimerTask(), period, delay);
} catch (Exception e) {
return false;
}
}
}
class B {
public boolean execute() {
try {
Timer t = new Timer();
t.schedule (new BTimerTask(), period, delay);
} catch (Exception e) {
return false;
}
}
}
Devo semplicemente sostituire le istanze Timer in classe A e classe B con ScheduledExecutorService e rendere la classe ATimerTask e BTimerTask in una classe Runnable, ad esempio
class B {
public boolean execute() {
try {
final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
scheduler.scheduleWithFixedDelay (new BRunnnableTask(), period, delay);
} catch (Exception e) {
return false;
}
}
}
È corretto.
EDIT: una delle principali motivazioni del porting è che le eccezioni di runtime lanciate in TimerTask uccidono quel thread e non possono essere programmate ulteriormente. Voglio evitare il caso in modo che, anche se ho un'eccezione di runtime, il thread dovrebbe continuare a essere eseguito e non arrestarsi.
Soluzione
NOTA: il modo in cui lo hai fatto perderà i thread!
Se la tua classe B
sarà mantenuta in giro e ogni istanza alla fine verrà chiusa o chiusa o rilasciata, lo farei così:
class B {
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public boolean execute() {
try {
scheduler.scheduleWithFixedDelay(new BRunnnableTask(), period, delay);
return true;
} catch (Exception e) {
return false;
}
}
public void close() {
scheduler.shutdownNow();
}
}
Se non eseguirai questo tipo di pulizia su ogni istanza, farei invece questo:
class B {
static final ScheduledExecutorService SCHEDULER = Executors.newCachedThreadPool();
public boolean execute() {
try {
SCHEDULER.scheduleWithFixedDelay(new BRunnnableTask(), period, delay);
return true;
} catch (Exception e) {
return false;
}
}
}
Ogni ExecutorService
che assegni nel tuo codice alloca un singolo Thread
. Se crei molte istanze della tua classe B
, a ciascuna istanza verrà assegnato un Thread
. Se questi non raccolgono rapidamente la spazzatura, allora puoi finire con molte migliaia di thread allocati (ma non utilizzati, appena allocati) e puoi arrestare l'intero server, facendo morire di fame ogni processo sulla macchina, non solo la tua JVM. L'ho visto accadere su Windows e mi aspetto che possa succedere anche su altri sistemi operativi.
Un pool di thread nella cache statica è molto spesso una soluzione sicura quando non si intende utilizzare i metodi del ciclo di vita sulle singole istanze di oggetti, poiché si manterranno solo tutti i thread che sono effettivamente in esecuzione e non uno per ogni istanza che crei che non è ancora stata raccolta in modo inutile.
Altri suggerimenti
Sembra ok. A seconda di cosa stai facendo, potresti voler mantenere il servizio di esecutore come membro in modo da poterlo riutilizzare. Inoltre, è possibile recuperare un ScheduledFuture dai metodi scheduleXX (). Questo è utile perché puoi richiamare get () su di esso per estrarre eventuali eccezioni che si verificano nel thread a tempo nel thread di controllo per la gestione.
Al momento non esiste un buon modo per gestire le attività ricorrenti nel framework Executors.
In realtà non è stato progettato tenendo presente questo caso d'uso e non esiste un modo realistico per evitare di ingoiare eccezioni.
Se devi davvero usarlo per ripetere le attività, ogni pianificazione dovrebbe assomigliare a questa:
scheduler.scheduleWithFixedDelay(new Runnable() {
public void run() {
try {
.. your normal code here...
} catch (Throwable t) {
// handle exceptions there
}
}
}, period, delay);