Introduzione
Con l’aumento del numero di nuclei disponibili nei processori oggi nel giorno, lungo Con la crescente necessità di ottenere prestazioni maggiori, le API multi-filettate stanno diventando piuttosto popolari. Java fornisce la propria cornice multi-filo chiamata EXECUOR FRAME.
Che cos’è Framework Executor?
Framework Executor contiene una serie di componenti utilizzati per gestire in modo efficiente i thread del lavoro. L’API dell’esecutore disaccene l’esecuzione dell’attività Task effettiva che verrà eseguita da Executors
. Questo design è una delle implementazioni del modello di produttore-consumatore.
java.util.concurrent.Executors
Fornire metodi di fabbrica che vengono utilizzati per creare ThreadPools
dei fili di lavoro.
Per utilizzare il framework di esecuzione, è necessario creare uno di quei gruppi di sottoprogazioni e inviare l’attività per l’esecuzione. È il lavoro del programma di esecutore framework ed esegui le attività inviate e restituiscono i risultati del gruppo subprocess.
Una domanda di base in cui viene in mente è il motivo per cui abbiamo bisogno di tali gruppi di sottoprocessioni quando possiamo creare oggetti di java.lang.Thread
o implementare Runnable
/ Callable
Interfacce per ottenere il parallelismo?
La risposta è ridotta a due fatti di base:
- La creazione di un nuovo thread per una nuova attività implica un sovraccarico di creazione e smontaggio del filo. La gestione del ciclo di vita di questo thread aumenta significativamente il tempo di esecuzione.
- Aggiungi un nuovo filo per ciascun processo senza alcun tipo di limitazione conduce alla creazione di un numero elevato di thread. Queste sottoprocessi occupano la memoria e causano risorse dei rifiuti. La CPU inizia a dedicare troppo tempo per modificare il contesto quando viene sostituito ogni filo e viene immesso un altro thread per l’esecuzione.
Tutti questi fattori riducono le prestazioni del sistema. I gruppi di sottoprocessioning superano questo problema mantenendo i fili vivi e lo riutilizzano. Eventuali attività in eccesso che fluiscono da quali sottoprocessi al gruppo possono gestire sono mantenute in un Queue
. Una volta rilasciati uno dei thread, occupano il prossimo compito di questa coda. Questa fattura è essenzialmente illimitata per gli esecutori pronti per l’uso forniti dal JDK.
Tipi di esecuzioni
Ora che abbiamo una buona idea di ciò che un esecutore è, andiamo Dai un’occhiata anche ai diversi tipi di esecutori.
SinglethreadExecucructor
Questo esemploratore del gruppo sottoprocesso ha solo un thread. Viene utilizzato per eseguire compiti in sequenza. Se il thread muore a causa di un’eccezione durante l’esecuzione di un’attività, viene creata una nuova filettatura per sostituire il vecchio filato e le attività successive vengono eseguite nel nuovo.
ExecutorService executorService = Executors.newSingleThreadExecutor()
PROCESSIBILETROTHADPOOL (N)
Come il nome indica, è un gruppo di sottoprocessi di un numero fisso di sottoprocessi. Le attività inviate all’esecutore vengono eseguite da n
Threads e se ci sono più attività memorizzate in LinkedBlockingQueue
. Questo numero è solitamente il numero totale di sottoprogasse supportate dal processore sottostante.
ExecutorService executorService = Executors.newFixedThreadPool(4);
cachedthreadpool
Questo gruppo di sottoprocessi è usato principalmente quando Ci sono molti compiti paralleli di breve durata da eseguire. A differenza del gruppo di fili fissi, il numero di sottoprocessi in questo gruppo di esecuzioni non è limitato. Se tutte le thread sono occupati a eseguire alcune attività e uno nuovo arriva, il gruppo creerà e aggiungerà una nuova sottoprocessa all’esecutore. Non appena una delle sottoprocessi è GRATUITA, riprenderà l’esecuzione dei nuovi compiti. Se un thread rimane inattivo per sessanta secondi, sono finiti e rimossi dalla cache.
Tuttavia, se non è somministrato correttamente o le attività non sono di breve durata, il gruppo di sottoprocessi avrà molti attivi sottoprocessi. Questo può portare a un’eliminazione delle risorse e, pertanto, a una caduta di prestazioni.
ExecutorService executorService = Executors.newCachedThreadPool();
Fiscale programmato
Questo esecutore è usato quando noi Avere un compito che deve essere eseguito a intervalli regolari o se vogliamo ritardare un determinato attività.
ScheduledExecutorService scheduledExecService = Executors.newScheduledThreadPool(1);
Le attività possono essere programmate in utilizzando uno dei due metodi scheduleAtFixedRate
o scheduleWithFixedDelay
.
scheduledExecService.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
scheduledExecService.scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)
La differenza principale tra i due metodi è la sua interpretazione del ritardo tra le esecuzioni consecutive del lavoro programmato.
scheduleAtFixedRate
Esegue l’attività con un intervallo fisso, indipendentemente da quando il compito precedente è terminato.
scheduleWithFixedDelay
avvia il conto alla rovescia del ritardo solo dopo aver completato l’attività corrente.
Comprendere l’oggetto futuro
È possibile accedere al risultato dell’attività inviata per l’esecuzione a un Executor utilizzando java.util.concurrent.Future
oggetto restituito dall’esecutore. Il futuro può essere considerato come una promessa fatta dall’esecutore al chiamante.
Future<String> result = executorService.submit(callableTask);
Un compito inviato all’esecutore, come quello precedente, è asincrono, Cioè, l’esecuzione del programma non attende l’esecuzione dell’attività per passare al passaggio successivo. Invece, ogni volta che è stata completata l’esecuzione dell’attività, è stabilita in questo Future
oggetto da parte dell’Albacea.
Il chiamante può continuare a eseguire il programma principale e Quando è necessario il risultato dell’attività inviata, è possibile chiamare .get()
in questo oggetto Future
Oggetto. Se il compito è completato, il risultato viene immediatamente restituito al chiamante o, in caso contrario, il chiamante è bloccato fino a quando l’esecutore è completato l’esecuzione e il risultato viene calcolato.
Se la persona che chiama non può permettersi indefinitamente Recupero del risultato, questa attesa può anche essere cronometrata. Questo è ottenuto dal metodo Future.get(long timeout, TimeUnit unit)
che produce un TimeoutException
se il risultato non viene restituito entro il periodo stabilito. Il chiamante può gestire questa eccezione e continuare con la successiva esecuzione del programma.
Se è presente un’eccezione durante l’esecuzione dell’attività, il metodo GET creerà un ExecutionException
.
Qualcosa di importante sul risultato restituito da metodo è che viene restituito solo se l’attività inviata implements java.util.concurrent.Callable
. Se l’attività implementa il Runnable
interfaccia, la chiamata a .get()
restituire null
L’attività è completa.
Un altro metodo importante è il metodo . Questo metodo viene utilizzato per annullare l’esecuzione di un’attività inviata. Se l’attività è già in esecuzione, l’esecutore proverà a interrompere l’esecuzione dell’attività se mayInterruptIfRunning
Il flag è passato come true
.
Esempio: Creare ed eseguire un semplice esecutore
Ora creeremo un’attività e cercheremo di eseguirlo in un esecutore di gruppo fisso:
public class Task implements Callable<String> { private String message; public Task(String message) { this.message = message; } @Override public String call() throws Exception { return "Hello " + message + "!"; }}
The Task
IMPLEMENTI DI CLASSE Callable
ed è parametrizzato per String
Tipo. Viene anche dichiarato lancio di Exception
. Questa capacità di avviare un’eccezione all’esecutore e all’esecutore che restituisce questa eccezione al chiamante è di grande importanza perché aiuta la persona a chiamare per conoscere lo stato dell’esecuzione dell’attività.
Ora eseguiamo questa attività:
public class ExecutorExample { public static void main(String args) { Task task = new Task("World"); ExecutorService executorService = Executors.newFixedThreadPool(4); Future<String> result = executorService.submit(task); try { System.out.println(result.get()); } catch (InterruptedException | ExecutionException e) { System.out.println("Error occured while executing the submitted task"); e.printStackTrace(); } executorService.shutdown(); }}
Qui abbiamo creato un FixedThreadPool
Executor con un conteggio di 4 fili come questa dimostrazione è sviluppata in A Processore a quattro core. Il numero di sottoprocessi può essere maggiore dei core del processore se le attività eseguite eseguono considerevoli operazioni di I / O o trascorrere del tempo in attesa di risorse esterne.
Abbiamo istanziato il Task
classe e portalo all’esecutore per l’esecuzione. Il risultato viene restituito da Future
oggetto, che quindi stampa sullo schermo.
Eseguiamo il ExecutorExample
e verifica la tua uscita:
Hello World!
Come previsto, l’attività aggiunge il saluto “ciao” e restituisce il risultato attraverso il Future
Oggetto.
Infine, chiamiamo al momento del executorService
oggetto per completare tutte le filettature e restituire risorse al sistema operativo.
.shutdown()
Il metodo è attualmente il completamento delle attività attualmente inviate all’esecutore. Tuttavia, se il requisito è immediatamente chiudendo l’esecutore senza attesa, possiamo usare il .shutdownNow()
Metodo in posizione.
Qualsiasi attività di esecuzione in sospeso verrà restituita in un oggetto java.util.List
oggetto.
Inoltre possiamo creare questo compito implementando il Runnable
interfaccia:
public class Task implements Runnable{ private String message; public Task(String message) { this.message = message; } public void run() { System.out.println("Hello " + message + "!"); }}
C’è una coppia Di importanti cambiamenti qui quando implementiamo Runnable.
- Il risultato dell’esecuzione dell’attività non può essere restituito dal metodo
run()
. Pertanto, stiamo stampando direttamente da qui. - The
run()
Il metodo non è configurato per avviare alcuna eccezione contrassegnata.
conclusione
La subprocessa multipla sta diventando sempre più comune poiché la velocità dell’orologio del processore è difficile da aumentare. Tuttavia, la gestione del ciclo di vita di ogni filo è molto difficile a causa della complessità coinvolta.
In questo articolo, abbiamo dimostrato un fotogramma multiplo multiplo ma semplice, un quadro di esecuzione e ha spiegato i suoi diversi componenti. Diamo anche un’occhiata a diversi esempi di creazione ed esecuzione di compiti in un esecutore.
Come sempre, il codice di questo esempio può essere trovato in GitHub.