Java Concurrence: il quadro di esecuzione

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.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *