concurrency java semaphore
Questo tutorial discuterà i componenti del pacchetto java.util.concurrent come Java Semaphore, Executor Framework, ExecutorService per implementare la concorrenza in Java:
Dai nostri precedenti tutorial Java, sappiamo che la piattaforma Java supporta la programmazione concorrente da zero. L'unità di base della concorrenza è un thread e abbiamo discusso in dettaglio di thread e multithreading in Java.
Da Java 5 in poi, un pacchetto chiamato 'java.util.concurrent' è stato aggiunto alla piattaforma Java. Questo pacchetto contiene una serie di classi e librerie che rendono più facile per il programmatore sviluppare applicazioni concorrenti (multi-thread). Utilizzando questo pacchetto, non dobbiamo scrivere classi complesse poiché abbiamo implementazioni pronte della maggior parte dei concetti concorrenti.
=> Controlla TUTTI i tutorial Java qui.
In questo tutorial, discuteremo i vari componenti del pacchetto java.util.concurrent riguardanti la concorrenza e il multithreading in Java.
Cosa imparerai:
pacchetto java.util.concurrent
Di seguito sono elencati i vari componenti del pacchetto java.util.concurrent relativi alla concorrenza e al multithreading in Java. Esploriamo ogni componente in dettaglio con l'aiuto di semplici esempi di programmazione. Alcuni dei componenti lo faremo
discutere sono:
- Framework esecutore
- ExecutorService
- ThreadPool
- Richiamabile
- Serrature - ReentrantLock
- Semaforo
- ForkJoinPool
Executor Framework in Java
Executor Framework in Java è stato rilasciato con la versione 5 di JDK. Executor Framework (java.util.concurrent.Executor) è un framework costituito da componenti che ci aiutano a gestire in modo efficiente più thread.
Utilizzando Executor Framework, possiamo eseguire oggetti eseguibili riutilizzando i thread già esistenti. Non è necessario creare nuovi thread ogni volta che è necessario eseguire oggetti.
L'API Executor separa o disaccoppia l'esecuzione di un'attività dall'attività effettiva utilizzando un file Esecutore . Un esecutore è centrato sull'interfaccia dell'Executor e dispone di interfacce secondarie, ad es. ExecutorService e la classe ThreadPoolExecutor.
Quindi usando Executor, dobbiamo solo creare oggetti Runnable e poi inviarli all'esecutore che li esegue.
Alcune delle migliori pratiche da seguire durante l'utilizzo del framework Executor sono:
- Dovremmo effettuare un controllo incrociato e pianificare un codice per rivedere gli elenchi principali in modo da poter rilevare deadlock e livelock nel codice.
- Il codice Java deve essere sempre eseguito su strumenti di analisi statica. Esempi degli strumenti di analisi statica sono FindBugs e PMD.
- Non dovremmo solo rilevare le eccezioni, ma anche gli errori nei programmi multi-thread.
Ora parliamo dei componenti di Executor Framework in Java.
Esecutore
L'esecutore può essere definito come un'interfaccia utilizzata per rappresentare un oggetto che esegue le attività fornite. Se l'attività deve essere eseguita sul thread corrente o nuovo dipende dal punto da cui è stata avviata la chiamata, che dipende ulteriormente dall'implementazione.
Quindi, utilizzando Executor, possiamo scollegare le attività dall'attività effettiva e quindi eseguirle in modo asincrono.
Tuttavia, l'esecuzione dell'attività utilizzando Executor non deve essere asincrona. Gli esecutori possono anche richiamare l'attività istantaneamente utilizzando il thread invocando.
Di seguito è riportato un esempio di codice per creare l'istanza di Executor:
public class Invoker implements Executor { @Override public void execute (Runnable r_interface) { r_interface.run(); } }
Una volta creato l'invoker, come mostrato sopra, possiamo usarlo per eseguire l'attività come segue.
public void execute () { Executor executor = new Invoker (); executor.execute ( () -> { //perform task }); }
Si noti che se l'attività non viene accettata dall'Executor, viene generata RejectedExecutionException.
ExecutorService
Un ExecutorService (java.util.concurrent.ExecutorService) pianifica le attività inoltrate in base alla disponibilità dei thread e mantiene anche una coda di memoria. ExecutorService funge da soluzione completa per l'elaborazione asincrona delle attività.
come scrivere casi di test di regressione
Per utilizzare ExecutorService nel codice, creiamo una classe Runnable. ExecutorService gestisce un pool di thread e assegna anche le attività ai thread. Le attività possono anche essere messe in coda nel caso in cui il thread non sia disponibile.
Di seguito è riportato un semplice esempio di ExecutorService.
import java.util.concurrent.*; public class Main { public static void main(String() args) { //create ExecutorService instance with 10 threads ExecutorService executor_Service = Executors.newFixedThreadPool(10); //assign the service to Runnable instance executor_Service.execute(new Runnable() { @Override public void run() { //print the message System.out.println('Simple Example of ExecutorService!!!'); } }); //shutdown executorService executor_Service.shutdown(); } }
Produzione
Nel programma sopra, creiamo una semplice istanza ExecutorService con un pool di thread composto da 10 thread. Viene quindi assegnato all'istanza Runnable ed eseguito per stampare il messaggio precedente. Dopo aver stampato il messaggio, ExecutorService viene chiuso.
Pool di thread
Un pool di thread in Java è un gruppo di thread di lavoro che possono essere riutilizzati molte volte e assegnati a lavori.
Un pool di thread contiene un gruppo di thread di dimensioni fisse. Ogni thread viene estratto dal pool di thread e assegnato a un'attività dal provider di servizi. Una volta completato il lavoro assegnato, il thread viene assegnato nuovamente al pool di thread.
Il pool di thread è vantaggioso in quanto non è necessario creare un nuovo thread ogni volta che l'attività è disponibile, quindi le prestazioni sono migliorate. Viene utilizzato nelle applicazioni in tempo reale che utilizzano Servlet e JSP in cui i pool di thread vengono utilizzati per elaborare le richieste.
Nelle applicazioni multi-thread, il pool di thread consente di risparmiare risorse e aiuta a contenere il parallelismo entro limiti predefiniti.
Il seguente programma Java mostra il pool di thread in Java.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class WorkerThreadClass implements Runnable { private String message; //thread class constructor public WorkerThreadClass(String s){ this.message=s; } //run method for thread public void run() { System.out.println(' Start: '+message); processmessage(); //sleep between start and end System.out.println(' End: '+ message); } //processmessage method => sleeps the thread for 2 sec private void processmessage() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Main { public static void main(String() args) { //create a ExecutorService instance ExecutorService executor = Executors.newFixedThreadPool(5);//creating a pool of 5 threads //create thread instances and execute them for (int i = 0; i <5; i++) { Runnable workerThrd = new WorkerThreadClass('Thread_' + i); executor.execute(workerThrd);//calling execute method of ExecutorService } //shutdown ExecutorService executor.shutdown(); while (!executor.isTerminated()) { } System.out.println('Finished all threads'); } }
Produzione
Nei programmi sopra, c'è un pool di thread di 5 thread che vengono creati utilizzando il metodo 'newFixedThreadPool'. Quindi i thread vengono creati e aggiunti al pool e assegnati a ExecutorService per l'esecuzione.
Richiamabile in Java
Sappiamo già che possiamo creare thread utilizzando due approcci. Un approccio consiste nell'estendere la classe Thread mentre il secondo nell'implementazione di un'interfaccia Runnable.
Tuttavia, i thread creati utilizzando l'interfaccia Runnable mancano di una funzionalità, ovvero non restituisce un risultato quando il thread viene terminato o run () completa l'esecuzione. È qui che entra in gioco l'interfaccia Callable.
Utilizzando un'interfaccia richiamabile definiamo un'attività in modo che restituisca un risultato. Potrebbe anche generare un'eccezione. L'interfaccia Callable fa parte del pacchetto java.util.concurrent.
L'interfaccia Callable fornisce un metodo call () che si trova sulle linee simili al metodo run () fornito dall'interfaccia Runnable con la sola differenza che il metodo call () restituisce un valore e genera un'eccezione selezionata.
Il metodo call () dell'interfaccia Callable ha il seguente prototipo.
public Object call () throws Exception;
Poiché il metodo call () restituisce un oggetto, il thread principale deve esserne consapevole.
Quindi il valore restituito dovrebbe essere memorizzato in un altro oggetto noto al thread principale. Questo scopo è servito utilizzando un oggetto 'Futuro'. Un oggetto Future è un oggetto che contiene il risultato restituito da un thread. O in altre parole, manterrà il risultato quando Callable ritorna.
Callable incapsula un'attività che dovrebbe essere eseguita su un altro thread. Un oggetto Future archivia il risultato restituito da un thread diverso.
Un'interfaccia richiamabile non può essere utilizzata per creare un thread. Abbiamo bisogno di Runnable per creare un thread. Quindi per memorizzare il risultato è necessario un oggetto Future. Java fornisce un tipo concreto denominato 'FutureTask' che combina le funzionalità implementando sia Runnable che Future.
Creiamo un FutureTask fornendo un costruttore con Callable. Questo oggetto FutureTask viene quindi fornito al costruttore della classe Thread per creare un oggetto Thread.
Di seguito è riportato un programma Java che mostra l'interfaccia Callable e l'oggetto Future. Usiamo anche l'oggetto FutureTask in questo programma.
Come già accennato, nel programma creiamo una classe che implementa un'interfaccia Callable con un metodo call () sovrascritto. Nel metodo principale creiamo 10 oggetti FutureTask. Ogni costruttore di oggetti ha un oggetto di classe Callable come argomento. Quindi l'oggetto FutureTask è associato a un'istanza di thread.
Quindi indirettamente creiamo un thread utilizzando un oggetto dell'interfaccia Callable.
import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; //create a class implementing Callable interface class CallableDemo implements Callable { //define call () method public Object call() throws Exception { Random generator = new Random(); Integer randomNumber = generator.nextInt(10); Thread.sleep(randomNumber * 1000); return randomNumber; } } public class Main { public static void main(String() args) throws Exception { // Array of FutureTask objects FutureTask() randomNumberTasks = new FutureTask(10); for (int i = 0; i <10; i++) { Callable callable = new CallableDemo(); // Create the FutureTask with Callable class randomNumberTasks(i) = new FutureTask(callable); // create thread with FutureTask Thread t = new Thread(randomNumberTasks(i)); //start the thread t.start(); } System.out.println('The contents of FutureTask objects:'); for (int i = 0; i < 10; i++) { // get() contents of FutureTask System.out.print(randomNumberTasks(i).get() + ' '); } } }
Produzione
Come mostrato nel programma precedente, il metodo call () di Callable che è sovrascritto nella classe che implementa Callable genera numeri casuali. Una volta avviato il thread, vengono visualizzati questi numeri casuali.
Inoltre, usiamo oggetti FutureTask nella funzione principale. Poiché implementa l'interfaccia Future, non è necessario memorizzare i risultati negli oggetti Thread. Allo stesso modo, possiamo annullare l'attività, verificare se è in esecuzione o completata e anche ottenere il risultato utilizzando l'oggetto FutureTask.
ReentrantLock in Java
Abbiamo discusso in dettaglio la sincronizzazione dei thread utilizzando la parola chiave sincronizzata nel nostro ultimo tutorial. L'uso della parola sincronizzata per la sincronizzazione dei thread è il metodo di base ed è alquanto rigido.
Utilizzando la parola chiave sincronizzata, un thread può bloccarsi solo una volta. Inoltre, dopo che un thread esce dal blocco sincronizzato, il thread successivo prende il blocco. Non ci sono code di attesa. Questi problemi possono causare la fame di qualche altro thread in quanto potrebbe non accedere alle risorse per molto tempo.
Per risolvere questi problemi, abbiamo bisogno di un metodo flessibile per sincronizzare i thread. Il 'Reentrant Locks' è questo metodo in Java che fornisce la sincronizzazione con una flessibilità molto maggiore.
La classe 'ReentrantLock' implementa i blocchi Reentrant ed è una parte del pacchetto 'import java.util.concurrent.locks'. La classe ReentrantLock fornisce la sincronizzazione del metodo per accedere alle risorse condivise. Le classi hanno anche i metodi di blocco e sblocco per bloccare / sbloccare le risorse quando si accede da thread.
Una caratteristica peculiare di ReentrantLock è che il thread può bloccare la risorsa condivisa più di una volta utilizzando ReentrantLock. Fornisce il conteggio dei blocchi che è impostato su uno quando il thread blocca la risorsa.
Il thread può rientrare e accedere alla risorsa prima di sbloccarsi. Ogni volta che il thread accede alla risorsa utilizzando il blocco Reentrant, il conteggio delle trattenute viene incrementato di uno. Per ogni sblocco, il conteggio delle trattenute viene diminuito di uno.
Quando il conteggio delle trattenute raggiunge 0, la risorsa condivisa viene sbloccata.
La classe ReentrantLock fornisce anche un parametro di equità che è un valore booleano che può essere passato con il costruttore del lucchetto. Quando il parametro di equità è impostato su true, ogni volta che un thread rilascia il blocco, il blocco viene passato al thread in attesa più lungo. Questo previene la fame.
Le serrature Reentrant possono essere utilizzate come segue:
return_type method_name() { reentrantlock.lock(); try { //Do some work } catch(Exception e) { e.printStackTrace(); } finally { reentrantlock.unlock(); } }
Nota che l'istruzione di sblocco per ReentrantLock è sempre nel blocco finalmente. Ciò garantisce che il blocco venga rilasciato anche se viene generata un'eccezione.
Implementiamo un programma Java per comprendere ReentrantLock.
tipi di funzioni c ++
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.*; import java.util.concurrent.locks.ReentrantLock; //thread class that implements Runnable interface class ThreadClass implements Runnable { String task_name; //define ReentrantLock object ReentrantLock thrd_lck; //ThreadClass constructor initialized lock and task name public ThreadClass(ReentrantLock r_lock, String t_name) { thrd_lck = r_lock; task_name = t_name; } //thread run () method public void run() { boolean bool_val = false; while (!bool_val) { //check for Outer Lock boolean tryLock_val = thrd_lck.tryLock(); // if lock is free, do the following if(tryLock_val) { try { for(int i=0;i<=6;i++) { if(i>=2) { thrd_lck.lock(); Thread thread_one = new Thread(); System.out.println('Thread Created.....'); if(i==3) { thread_one.setName('Maint Thread2'); System.out.println('Thread Created.....'); } } if(i==4) thrd_lck.unlock(); break; } System.out.println('ReentrantLock=>Is locked after sleep(1500) : ' + thrd_lck.isLocked()); System.out.println('Work done for task : ' + task_name ); bool_val = true; } catch(Exception e) { e.printStackTrace(); } } } } } public class Main { public static void main(String() args) { //define ReentrantLock lock object and service pool ReentrantLock reentrant_lock = new ReentrantLock(); ExecutorService pool = Executors.newFixedThreadPool(2); //create thread instance and pass lock and task name Runnable worker_thread = new ThreadClass(reentrant_lock, 'ThreadJob'); //execute the thread in exec pool pool.execute(worker_thread); //shut down the pool pool.shutdown(); } }
Produzione
Nel programma sopra, abbiamo creato un thread e utilizzato ReentrantLock per questo. Utilizzando ReentrantLock è possibile accedere alla risorsa condivisa.
Semaforo in Java
Il metodo successivo di sincronizzazione dei thread consiste nell'usare Semaphore. Usando questo costrutto chiamato semaforo, l'accesso a una risorsa condivisa è controllato tramite un contatore. I segnali vengono inviati tra i thread in modo da poter proteggere la sezione critica e anche evitare segnali persi.
Un semaforo può essere definito come una variabile utilizzata per gestire processi simultanei sincronizzando questi processi. I semafori vengono utilizzati anche per sincronizzare l'accesso alla risorsa condivisa e quindi evitare una condizione di competizione. L'autorizzazione data a un thread per accedere alla risorsa condivisa tramite semaforo è anche chiamata autorizzazione.
A seconda delle funzioni che svolgono, i semafori possono essere suddivisi in due tipi:
# 1) semaforo binario: Un semaforo binario viene utilizzato per sincronizzare processi simultanei e implementare l'esclusione reciproca. Un semaforo binario assume solo due valori, ovvero 0 e 1.
# 2) Conteggio del semaforo: Il semaforo di conteggio ha un valore che indica il numero di processi che possono entrare nella sezione critica. In qualsiasi punto, il valore indica il numero massimo di processi che entrano nella sezione critica.
Allora come funziona un semaforo?
Il funzionamento di un semaforo può essere riassunto nei seguenti passaggi:
- Se il conteggio del semaforo> 0, significa che il thread ha un permesso per accedere alla sezione critica, e quindi il conteggio viene decrementato.
- In caso contrario, il thread viene bloccato fino all'acquisizione del permesso.
- Quando il thread è terminato con l'accesso alla risorsa condivisa, il permesso viene rilasciato e il conteggio dei semafori viene incrementato in modo che un altro thread possa ripetere i passaggi precedenti e acquisire il permesso.
I passaggi precedenti del funzionamento dei semafori possono essere riassunti nel diagramma di flusso sottostante.
In Java, non è necessario implementare il nostro semaforo ma fornisce un file Semaforo classe che implementa la funzionalità del semaforo. La classe Semaphore fa parte di java.util.concurrent pacchetto.
La classe Semaphore fornisce i seguenti costruttori utilizzando i quali è possibile creare oggetti semaforo:
Semaphore (int num_value) Semaphore (int num_value, boolean how)
Qui,
num_value => valore iniziale del conteggio dei permessi che determina il numero di thread che possono accedere alla risorsa condivisa.
come => imposta l'ordine in cui ai thread verranno concessi i permessi (how = true). Se how = false, non viene seguito alcun tale ordine.
Ora implementeremo un programma Java che dimostrerà il semaforo utilizzato per gestire l'accesso alle risorse condivise e prevenire la race condition.
import java.util.concurrent.*; //class for shared resource class SharedRes { static int count = 0; } class ThreadClass extends Thread { Semaphore sem; String threadName; public ThreadClass(Semaphore sem, String threadName) { super(threadName); this.sem = sem; this.threadName = threadName; } @Override public void run() { // Thread T1 processing if(this.getName().equals('T1')) { System.out.println('Start: ' + threadName); try { System.out.println(threadName + ' :waiting for a permit.'); // acquire the permit sem.acquire(); System.out.println(threadName + ':Acquired permit'); // access shared resource for(int i=0; i <5; i++) { SharedRes.count++; System.out.println(threadName + ': ' + SharedRes.count); Thread.sleep(10); } } catch (InterruptedException exc) { System.out.println(exc); } // Release the permit. System.out.println(threadName + ':Released the permit'); sem.release(); } // Thread T2 processing else { System.out.println('Start: ' + threadName); try { System.out.println(threadName + ':waiting for a permit.'); // acquire the lock sem.acquire(); System.out.println(threadName + ':Acquired permit'); // process the shared resource for(int i=0; i < 5; i++) { SharedRes.count--; System.out.println(threadName + ': ' + SharedRes.count); Thread.sleep(10); } } catch (InterruptedException exc) { System.out.println(exc); } // Release the permit. System.out.println(threadName + ':Released the permit.'); sem.release(); } } } public class Main { public static void main(String args()) throws InterruptedException { //create Semaphore=> #permits = 1 Semaphore sem = new Semaphore(1); // Create thread instances T1 & T2 //T1=> Increments the count; T2=> Decrements the count ThreadClass thread1 = new ThreadClass(sem, 'T1'); ThreadClass thread2 = new ThreadClass(sem, 'T2'); // start T1 & T2 thread1.start(); thread2.start(); // Wait T1 & T2 thread1.join(); thread2.join(); System.out.println('count: ' + SharedRes.count); // display final count. } }
Produzione
Questo programma ha dichiarato una classe per la risorsa condivisa. Dichiara inoltre una classe thread in cui è presente una variabile semaforo inizializzata nel costruttore della classe.
Nel metodo run () sovrascritto della classe Thread, l'elaborazione dell'istanza del thread viene eseguita in cui il thread acquisisce il permesso, accede a una risorsa condivisa e quindi rilascia il permesso.
Nel metodo principale, abbiamo dichiarato due istanze di thread. Entrambi i thread vengono quindi avviati e quindi attendono utilizzando il metodo di join. Infine, viene visualizzato il conteggio, ovvero 0 che indica che entrambi i thread hanno terminato con la risorsa condivisa.
Fork e unisciti a Java
Il framework fork / join è stato introdotto per la prima volta in Java 7. Questo framework è costituito da strumenti che possono accelerare l'elaborazione parallela. Utilizza tutti i core del processore disponibili nel sistema e completa l'attività. Il framework fork / join utilizza l'approccio divide et impera.
L'idea di base alla base del framework Fork / Join è che il primo framework 'Forks', ovvero suddivide ricorsivamente l'attività in attività secondarie individuali più piccole finché le attività non sono atomiche in modo che possano essere eseguite in modo asincrono.
Dopo aver fatto ciò, le attività vengono 'unite', ovvero tutte le attività secondarie vengono unite ricorsivamente in un'unica attività o valore restituito.
Il framework fork / join ha un pool di thread noto come 'ForkJoinPool'. Questo pool gestisce il tipo di thread di lavoro 'ForkJoinWorkerThread' fornendo così un'efficace elaborazione parallela.
ForkJoinPool gestisce i thread di lavoro e ci aiuta anche a ottenere informazioni relative alle prestazioni e allo stato del pool di thread. ForkJoinPool è un'implementazione del 'ExecutorService' di cui abbiamo discusso sopra.
A differenza dei thread di lavoro, ForkJoinPool non crea un thread separato per ogni attività secondaria. Ogni thread nel ForkJoinPool mantiene la sua deque (coda a doppia estremità) per memorizzare le attività.
Il deque funge da bilanciamento del carico di lavoro del thread e lo fa con l'aiuto di un 'algoritmo di furto del lavoro' descritto di seguito.
Algoritmo di furto del lavoro
Possiamo definire l'algoritmo del furto di lavoro in parole semplici come 'Se un thread è libero,' ruba 'il lavoro ai thread occupati'.
Un thread di lavoro riceverà sempre le attività dalla sua deque. Quando tutte le attività nel deque sono esaurite e il deque è vuoto, il thread di lavoro prenderà un'attività dalla coda di un altro deque o dalla 'coda di ingresso globale'.
In questo modo la possibilità che i thread competano per le attività è ridotta al minimo e anche il numero di volte in cui il thread deve cercare lavoro è ridotto. Questo perché il thread ha già ottenuto la maggior parte del lavoro disponibile e l'ha terminato.
Allora come possiamo usare ForkJoinPool in un programma?
La definizione generale di ForkJoinPool è la seguente:
public class ForkJoinPool extends AbstractExecutorService
La classe ForkJoinPool fa parte del pacchetto 'java.util.concurrent'.
In Java 8, creiamo un'istanza di ForkJoinPool utilizzando il suo metodo statico 'common-pool ()' che fornisce un riferimento al pool comune o al pool di thread predefinito.
formato del test case nel test del software
ForkJoinPool commonPool = ForkJoinPool.commonPool ();
In Java 7, creiamo un'istanza ForkJoinPool e la assegniamo al campo della classe di utilità come mostrato di seguito.
public static ForkJoinPool forkJoinPool = new ForkJoinPool(2);
La definizione precedente indica che il pool ha un livello di parallelismo di 2 tale che il pool utilizzerà 2 core del processore.
Per accedere al pool di cui sopra, possiamo fornire la seguente dichiarazione.
ForkJoinPool forkJoinPool = PoolUtil.forkJoinPool;
Il tipo di base per le attività ForkJoinPool è 'ForkJoinTask'. Dovremmo estendere una delle sue sottoclassi, ad esempio per le attività void, RecursiveAction e per le attività che restituiscono un valore, RecursiveTask. Entrambe le classi estese forniscono un metodo astratto compute () in cui definiamo la logica dell'attività.
Di seguito è riportato un esempio per dimostrare il ForkJoinPool.
import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; //class declaration for ForkJoinPool tasks class FJPoolTask extends RecursiveAction { private long Load = 0; public FJPoolTask(long Load) { this.Load = Load; } @Override protected void compute() { //if threshold is reached, break tasks into smaller tasks List subtasks = new ArrayList(); subtasks.addAll(createSubtasks()); for(RecursiveAction subtask : subtasks){ subtask.fork(); } } //create subtasks private List createSubtasks() { List sub_tasks =new ArrayList(); FJPoolTask sub_task1 = new FJPoolTask(this.Load / 2); FJPoolTask sub_task2 = new FJPoolTask(this.Load / 2); FJPoolTask sub_task3 = new FJPoolTask(this.Load / 2); sub_tasks.add(sub_task1); sub_tasks.add(sub_task2); sub_tasks.add(sub_task3); return sub_tasks; } } public class Main { public static void main(final String() arguments) throws InterruptedException { //get count of available processors int proc = Runtime.getRuntime().availableProcessors(); System.out.println('Processors available:' +proc); //declare forkJoinPool ForkJoinPool Pool = ForkJoinPool.commonPool(); System.out.println(' Active Threads (Before invoke):' +Pool.getActiveThreadCount()); //Declare ForkJoinPool task object FJPoolTask t = new FJPoolTask(400); //submit the tasks to the pool Pool.invoke(t); System.out.println(' Active Threads (after invoke):' +Pool.getActiveThreadCount()); System.out.println('Common Pool Size :' +Pool.getPoolSize()); } }
Produzione
Nel programma sopra, troviamo il numero di thread attivi nel sistema prima e dopo aver chiamato il metodo 'invoke ()'. Il metodo invoke () viene utilizzato per inviare le attività al pool. Troviamo anche il numero di core del processore disponibili nel sistema.
Domande frequenti
D # 1) Che cos'è Java Util Concurrent?
Risposta: Il pacchetto 'java.util.concurrent' è un insieme di classi e interfacce fornite da Java per facilitare lo sviluppo di applicazioni simultanee (multi-thread). Usando questo pacchetto possiamo usare direttamente l'interfaccia e le classi così come le API senza dover scrivere le nostre classi.
D # 2) Quali delle seguenti sono implementazioni simultanee presenti in java.util. pacchetto concorrente?
Risposta: Ad un livello elevato, il pacchetto java.util.concurrent contiene utilità come Executors, Synchronizer, Queues, Timings e Concurrent Collections.
D # 3) Cos'è Future Java?
Risposta: Un oggetto Future (java.util.concurrent.Future) viene utilizzato per memorizzare il risultato restituito da un thread quando viene implementata l'interfaccia Callable.
D # 4) Cos'è thread-safe in Java?
Risposta: Un codice o una classe thread-safe in Java è un codice o una classe che può essere condivisa in un ambiente multi-thread o simultaneo senza problemi e produce i risultati attesi.
D # 5) Cos'è la raccolta sincronizzata in Java?
Risposta: Una raccolta sincronizzata è una raccolta thread-safe. Il metodo synchronized collection () della classe java.util.Collections restituisce una raccolta sincronizzata (thread-safe).
Conclusione
Con questo tutorial, abbiamo completato l'argomento del multi-threading e della concorrenza in Java. Abbiamo discusso in dettaglio il multithreading nei nostri tutorial precedenti. Qui, abbiamo discusso la concorrenza e l'implementazione relativa alla concorrenza e al multithreading che fanno parte del pacchetto java.util.concurrent.
Abbiamo discusso altri due metodi di sincronizzazione, semafori e ReentrantLock. Abbiamo anche discusso del ForkJoinPool che viene utilizzato per eseguire le attività dividendole in attività più semplici e quindi unendo infine il risultato.
Il pacchetto java.util.concurrent supporta anche il framework Executor e gli esecutori che ci aiutano a eseguire i thread. Abbiamo anche discusso l'implementazione del pool di thread che consiste in thread riutilizzabili che vengono restituiti al pool al termine dell'esecuzione.
Abbiamo discusso un'altra interfaccia simile a Runnable che ci aiuta anche a restituire un risultato dal thread e l'oggetto Future utilizzato per memorizzare il risultato del thread ottenuto.
=> Guarda qui la serie di formazione su Java semplice.
Lettura consigliata
- Thread.Sleep () - Metodo Thread Sleep () in Java con esempi
- Distribuzione Java: creazione ed esecuzione di file JAR Java
- Nozioni di base su Java: sintassi Java, classe Java e concetti principali di Java
- Java Virtual Machine: come JVM aiuta nell'esecuzione di applicazioni Java
- Modificatori di accesso in Java - Tutorial con esempi
- Java sincronizzato: cos'è la sincronizzazione dei thread in Java
- Tutorial JAVA per principianti: oltre 100 tutorial video Java pratici
- Java Integer e Java BigInteger Class con esempi