Quali sono i vantaggi dell’utilizzo di un ExecutorService?

Qual è il vantaggio dell’utilizzo di ExecutorService su thread in esecuzione che passano un Runnable nel costruttore di Thread ?

ExecutorService astrae molte delle complessità associate alle astrazioni di livello inferiore come Raw Thread . Fornisce meccanismi per avviare, chiudere, inoltrare, eseguire e bloccare in modo sicuro la terminazione corretta o improvvisa dei compiti (espressa come Runnable o Callable ).

Da JCiP , Sezione 6.2, direttamente dalla bocca del cavallo:

Executor può essere un’interfaccia semplice, ma costituisce la base per una struttura flessibile e potente per l’esecuzione asincrona delle attività che supporta un’ampia varietà di politiche di esecuzione delle attività. Fornisce un mezzo standard per disaccoppiare l’ invio di attività dall’esecuzione dell’attività, descrivendo le attività come Runnable . Le implementazioni Executor forniscono anche supporto per il ciclo di vita e agganci per aggiungere raccolta di statistiche, gestione delle applicazioni e monitoraggio. … L’ utilizzo di un Executor è di solito il modo più semplice per implementare un progetto produttore-consumatore nella propria applicazione.

Piuttosto che spendere tempo implementando (spesso in modo errato e con grande sforzo) l’infrastruttura sottostante per il parallelismo, il framework juconcurrent ti permette invece di concentrarti sulla strutturazione di compiti, dipendenze, parallelismo potenziale. Per un’ampia gamma di applicazioni concorrenti, è semplice identificare e sfruttare i limiti delle attività e utilizzare juc , consentendo di concentrarsi sul sottogruppo molto più piccolo di vere sfide di concorrenza che potrebbero richiedere soluzioni più specializzate.

Inoltre, nonostante l’aspetto generale, la pagina dell’API Oracle che riassume le utilità di concorrenza include alcuni argomenti davvero validi per il loro utilizzo, non ultimo:

È probabile che gli sviluppatori comprendano già le classi di libreria standard, quindi non è necessario conoscere l’API e il comportamento dei componenti concorrenti ad-hoc. Inoltre, le applicazioni concorrenti sono molto più semplici da eseguire il debug quando sono costruite su componenti affidabili e ben testati.

Questa domanda su SO chiede di un buon libro, a cui la risposta immediata è JCiP. Se non l’hai già fatto, procurati una copia. L’approccio globale alla concorrenza presentato là va ben al di là di questa domanda, e ti farà risparmiare un sacco di angoscia a lungo termine.

Un vantaggio che vedo è nella gestione / pianificazione di diversi thread. Con ExecutorService, non è necessario scrivere il proprio gestore thread che può essere afflitto da bug. Questo è particolarmente utile se il tuo programma ha bisogno di eseguire più thread contemporaneamente. Ad esempio, si desidera eseguire due thread alla volta, è ansible farlo facilmente in questo modo:

 ExecutorService exec = Executors.newFixedThreadPool(2); exec.execute(new Runnable() { public void run() { System.out.println("Hello world"); } }); exec.shutdown(); 

L’esempio può essere banale, ma prova a pensare che la linea “ciao mondo” sia costituita da un’operazione pesante e desideri che l’operazione venga eseguita in più thread alla volta per migliorare le prestazioni del tuo programma. Questo è solo un esempio, ci sono ancora molti casi in cui si desidera pianificare o eseguire più thread e utilizzare ExecutorService come gestore di thread.

Per l’esecuzione di un singolo thread, non vedo alcun chiaro vantaggio dell’utilizzo di ExecutorService.

Di seguito sono riportati alcuni vantaggi:

  1. Il servizio Executor gestisce il thread in modo asincrono
  2. Usa callable per ottenere il risultato di ritorno dopo il completamento del thread.
  3. Gestisci l’allocazione del lavoro alla discussione libera e rivendita il lavoro completato dal thread per l’assegnazione automatica di nuovi lavori
  4. fork – join framework per l’elaborazione parallela
  5. Migliore comunicazione tra thread
  6. invokeAll e invokeAny danno più controllo per eseguire uno o tutti i thread contemporaneamente
  7. shutdown fornisce funzionalità per il completamento di tutto il lavoro assegnato al thread
  8. I servizi esecutivi pianificati forniscono metodi per produrre invocazioni ripetute di corse e callebles Spero che ti possa aiutare

Le seguenti limitazioni dal thread tradizionale sono state superate dal framework Executor (framework Thread Pool incorporato).

  • Scarsa gestione delle risorse, ovvero continua a creare nuove risorse per ogni richiesta. Nessun limite alla creazione di risorse. Usando il framework Executor possiamo riutilizzare le risorse esistenti e limitare la creazione di risorse.
  • Non robusto : se continuiamo a creare nuovi thread, otterremo un’eccezione StackOverflowException conseguenza la nostra JVM si arresterà in modo anomalo.
  • Overhead Creazione di tempo : per ogni richiesta abbiamo bisogno di creare nuove risorse. Creare una nuova risorsa richiede molto tempo. cioè Thread Creating> task. Usando il framework Executor possiamo essere compilati in Thread Pool.

Benefici del pool di thread

  • L’utilizzo del pool di thread riduce i tempi di risposta evitando la creazione di thread durante l’elaborazione di richieste o attività.

  • L’uso di Thread Pool consente di modificare la politica di esecuzione in base alle proprie esigenze. puoi passare da un thread singolo a più thread semplicemente sostituendo l’implementazione di ExecutorService.

  • Il pool di thread nell’applicazione Java aumenta la stabilità del sistema creando un numero configurato di thread decisi in base al carico del sistema e alla risorsa disponibile.

  • Thread Pool libera lo sviluppatore di applicazioni dai processi di gestione dei thread e consente di concentrarsi sulla logica di business.

fonte

È davvero così costoso creare un nuovo thread?

Come punto di riferimento, ho appena creato 60.000 thread con Runnable s con metodi run() vuoti. Dopo aver creato ciascun thread, ho chiamato immediatamente il suo metodo start(..) . Ciò ha richiesto circa 30 secondi di intensa attività della CPU. Esperimenti simili sono stati fatti in risposta a questa domanda . Il riassunto di quelli è che se i thread non terminano immediatamente e si accumula un numero elevato di thread attivi (poche migliaia), allora ci saranno problemi: (1) ogni thread ha uno stack, quindi si esaurirà la memoria (2) potrebbe esserci un limite al numero di thread per processo imposto dal sistema operativo, ma non necessariamente, a quanto pare .

Quindi, per quanto posso vedere, se parliamo di lancio diciamo 10 thread al secondo, e finiscono tutti più velocemente di quelli nuovi, e possiamo garantire che questa velocità non verrà superata troppo, quindi l’ExecutorService non offre alcun vantaggio concreto in termini di prestazioni o stabilità visibili. (Anche se può ancora rendere più conveniente o leggibile esprimere alcune idee di concorrenza nel codice.) D’altra parte, se si possono pianificare centinaia o migliaia di attività al secondo, che richiedono tempo per essere eseguite, si potrebbero incontrare grossi problemi immediamente. Ciò potrebbe verificarsi in modo imprevisto, ad esempio se si creano thread in risposta a richieste a un server e si verifica un picco nell’intensità delle richieste ricevute dal server. Ad esempio, un thread in risposta a ogni evento di input dell’utente (pressione di un tasto, movimento del mouse) sembra perfettamente a posto, purché le attività siano brevi.

ExecutorService dà anche accesso a FutureTask che restituirà alla class chiamante i risultati di un’attività in background una volta completata. Nel caso di implementazione di Callable

 public class TaskOne implements Callable { @Override public String call() throws Exception { String message = "Task One here. . ."; return message; } } public class TaskTwo implements Callable { @Override public String call() throws Exception { String message = "Task Two here . . . "; return message; } } // from the calling class ExecutorService service = Executors.newFixedThreadPool(2); // set of Callable types Set>callables = new HashSet>(); // add tasks to Set callables.add(new TaskOne()); callables.add(new TaskTwo()); // list of Future types stores the result of invokeAll() List>futures = service.invokeAll(callables); // iterate through the list and print results from get(); for(Futurefuture : futures) { System.out.println(future.get()); } 

Prima della versione 1.5 di java, Thread / Runnable è stato progettato per due servizi separati

  1. Unità di lavoro
  2. Esecuzione di quell’unità di lavoro

ExecutorService disaccoppia questi due servizi designando Runnable / Callable come unità di lavoro ed Executor come meccanismo per eseguire (con il ciclo di vita) l’unità di lavoro

La creazione di un numero elevato di thread senza restrizioni alla soglia massima può causare l’esaurimento della memoria heap dell’applicazione. Per questo motivo creare un ThreadPool è una soluzione molto migliore. Usando ThreadPool possiamo limitare il numero di thread che possono essere raggruppati e riutilizzati.

Il framework Executors facilita il processo di creazione dei pool di thread in java. La class Executors fornisce un’implementazione semplice di ExecutorService utilizzando ThreadPoolExecutor.

Fonte:

Cos’è il quadro degli esecutori