concurrency java semaphore
Ce tutoriel abordera les composants du package java.util.concurrent tels que Java Semaphore, Executor Framework, ExecutorService pour implémenter la concurrence en Java:
D'après nos précédents didacticiels Java, nous savons que la plate-forme Java prend en charge la programmation simultanée à partir de zéro. L'unité de base de la concurrence est un thread et nous avons discuté en détail des threads et du multithreading en Java.
À partir de Java 5, un package appelé «java.util.concurrent» a été ajouté à la plate-forme Java. Ce package contient un ensemble de classes et de bibliothèques qui permet au programmeur de développer plus facilement des applications simultanées (multi-threadées). En utilisant ce package, nous n'avons pas besoin d'écrire des classes complexes car nous avons des implémentations prêtes pour la plupart des concepts concurrents.
=> Consultez TOUS les didacticiels Java ici.
Dans ce tutoriel, nous aborderons les différents composants du package java.util.concurrent concernant la concurrence et le multithreading en Java.
Ce que vous apprendrez:
Package java.util.concurrent
Vous trouverez ci-dessous les différents composants du package java.util.concurrent concernant la concurrence et le multithreading en Java. Explorons chaque composant en détail à l’aide d’exemples de programmation simples. Certains des composants que nous allons
discuter sont:
- Cadre de l'exécuteur
- ExecutorService
- ThreadPool
- Appelable
- Serrures - ReentrantLock
- Sémaphore
- FourcheJoinPiscine
Framework Executor en Java
Le Framework Executor en Java a été publié avec la version JDK 5. Le Framework Executor (java.util.concurrent.Executor) est un framework qui se compose de composants qui nous aident à gérer efficacement plusieurs threads.
En utilisant le Framework Executor, nous pouvons exécuter des objets exécutables en réutilisant les threads déjà existants. Nous n'avons pas besoin de créer de nouveaux threads à chaque fois que nous devons exécuter des objets.
L'API Executor sépare ou dissocie l'exécution d'une tâche de la tâche réelle à l'aide d'un Exécuteur . Un exécuteur est centré sur l'interface Executor et a des sous-interfaces i.e. ExecutorService et la classe ThreadPoolExecutor.
Ainsi en utilisant Executor, il suffit de créer des objets Runnable et de les envoyer ensuite à l'exécuteur qui les exécute.
Certaines des meilleures pratiques à suivre lors de l'utilisation du framework Executor sont:
- Nous devrions vérifier et planifier un code pour examiner les listes supérieures afin que nous puissions détecter les blocages ainsi que les livelock dans le code.
- Le code Java doit toujours être exécuté sur des outils d'analyse statique. Exemples des outils d'analyse statique sont FindBugs et PMD.
- Nous devrions non seulement attraper les exceptions, mais aussi les erreurs dans les programmes multi-threads.
Parlons maintenant des composants d'Executor Framework en Java.
Exécuteur
L'exécuteur peut être défini comme une interface utilisée pour représenter un objet qui exécute les tâches qui lui sont fournies. Le fait que la tâche soit exécutée sur le thread actuel ou nouveau dépend du point à partir duquel l'appel a été lancé, ce qui dépend en outre de l'implémentation.
Ainsi, en utilisant Executor, nous pouvons dissocier les tâches de la tâche réelle, puis les exécuter de manière asynchrone.
Cependant, l'exécution de la tâche à l'aide d'Executor n'a pas besoin d'être asynchrone. Les exécuteurs peuvent également appeler la tâche instantanément en utilisant un thread.
Vous trouverez ci-dessous un exemple de code pour créer une instance d'Executor:
public class Invoker implements Executor { @Override public void execute (Runnable r_interface) { r_interface.run(); } }
Une fois que l'appelant est créé, comme indiqué ci-dessus, nous pouvons l'utiliser pour exécuter la tâche comme suit.
public void execute () { Executor executor = new Invoker (); executor.execute ( () -> { //perform task }); }
Notez que si la tâche n'est pas acceptée par l'exécuteur, elle lève RejectedExecutionException.
ExecutorService
Un ExecutorService (java.util.concurrent.ExecutorService) planifie les tâches soumises en fonction de la disponibilité des threads et maintient également une file d'attente de mémoire. Le ExecutorService agit comme une solution complète pour le traitement asynchrone des tâches.
Pour utiliser ExecutorService dans le code, nous créons une classe Runnable. ExecutorService gère un pool de threads et attribue également les tâches aux threads. Les tâches peuvent également être mises en file d'attente au cas où le thread ne serait pas disponible.
Vous trouverez ci-dessous un exemple simple d'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(); } }
Production
Dans le programme ci-dessus, nous créons une simple instance ExecutorService avec un pool de threads composé de 10 threads. Il est ensuite affecté à l'instance Runnable et exécuté pour imprimer le message ci-dessus. Après avoir imprimé le message, l'ExecutorService est arrêté.
Pool de threads
Un pool de threads en Java est un groupe de threads de travail qui peuvent être réutilisés plusieurs fois et affectés à des tâches.
Un pool de threads contient un groupe de threads de taille fixe. Chaque thread est extrait du pool de threads et une tâche est affectée par le fournisseur de services. Une fois le travail affecté terminé, le thread est à nouveau remis au pool de threads.
Le pool de threads est avantageux car nous n'avons pas à créer un nouveau thread chaque fois que la tâche est disponible, ce qui améliore les performances. Il est utilisé dans les applications en temps réel qui utilisent Servlet et JSP où des pools de threads sont utilisés pour traiter les demandes.
Dans les applications multithreads, le pool de threads économise des ressources et aide à contenir le parallélisme dans des limites prédéfinies.
Le programme Java ci-dessous illustre le pool de threads en 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'); } }
Production
Dans les programmes ci-dessus, il existe un pool de threads de 5 threads qui sont créés à l'aide de la méthode «newFixedThreadPool». Ensuite, les threads sont créés et ajoutés au pool et affectés à ExecutorService pour exécution.
Appelable en Java
Nous savons déjà que nous pouvons créer des threads en utilisant deux approches. Une approche consiste à étendre la classe Thread tandis que la seconde approche consiste à implémenter une interface exécutable.
Cependant, les threads créés à l'aide de l'interface Runnable manquent d'une fonctionnalité, c'est-à-dire qu'il ne renvoie pas de résultat lorsque le thread est terminé ou que run () termine l'exécution. C'est là que l'interface Callable entre en scène.
En utilisant une interface appelable, nous définissons une tâche afin qu'elle renvoie un résultat. Cela peut également lever une exception. L'interface Callable fait partie du package java.util.concurrent.
L'interface Callable fournit une méthode call () qui est sur les mêmes lignes que la méthode run () fournie par l'interface Runnable avec la seule différence que la méthode call () renvoie une valeur et lève une exception vérifiée.
La méthode call () de l'interface Callable a le prototype suivant.
meilleur stockage cloud pour les fichiers volumineux
public Object call () throws Exception;
Puisque la méthode call () retourne un Object, le thread principal doit en être conscient.
Par conséquent, la valeur de retour doit être stockée dans un autre objet connu du thread principal. Cet objectif est atteint en utilisant un objet «Future». Un objet Future est un objet qui contient le résultat renvoyé par un thread. Ou en d'autres termes, il contiendra le résultat lorsque Callable revient.
Callable encapsule une tâche qui doit s'exécuter sur un autre thread. Un objet Future stocke le résultat renvoyé par un thread différent.
Une interface appelable ne peut pas être utilisée pour créer un thread. Nous avons besoin de Runnable pour créer un thread. Ensuite, pour stocker le résultat, un objet Future est requis. Java fournit un type concret nommé «FutureTask» qui combine les fonctionnalités en implémentant à la fois Runnable et Future.
Nous créons une FutureTask en fournissant un constructeur avec Callable. Cet objet FutureTask est ensuite donné au constructeur de la classe Thread pour créer un objet Thread.
Vous trouverez ci-dessous un programme Java qui illustre l'interface Callable et l'objet Future. Nous utilisons également l'objet FutureTask dans ce programme.
Comme déjà mentionné, dans le programme, nous créons une classe qui implémente une interface Callable avec une méthode call () surchargée. Dans la méthode principale, nous créons 10 objets FutureTask. Chaque constructeur d'objet a un objet de classe Callable comme argument. Ensuite, l'objet FutureTask est associé à une instance de thread.
Par conséquent, indirectement, nous créons un thread en utilisant un objet d'interface 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() + ' '); } } }
Production
Comme indiqué dans le programme ci-dessus, la méthode call () de Callable qui est surchargée dans la classe implémentant Callable génère des nombres aléatoires. Une fois que le thread est lancé, il affiche ces nombres aléatoires.
En outre, nous utilisons des objets FutureTask dans la fonction principale. Comme il implémente l'interface Future, nous n'avons pas besoin de stocker les résultats dans les objets Thread. De même, nous pouvons annuler la tâche, vérifier si elle est en cours d'exécution ou terminée, et également obtenir le résultat à l'aide de l'objet FutureTask.
ReentrantLock en Java
Nous avons discuté en détail de la synchronisation des threads à l'aide du mot-clé synchronized dans notre dernier tutoriel. L'utilisation du mot synchronisé pour la synchronisation des threads est la méthode de base et est quelque peu rigide.
En utilisant le mot-clé synchronized, un thread ne peut se verrouiller qu'une seule fois. De plus, après qu'un thread quitte le bloc synchronisé, le thread suivant prend le verrou. Il n'y a pas de file d'attente. Ces problèmes peuvent entraîner la famine d'un autre thread car il peut ne pas accéder aux ressources pendant une longue période.
Pour résoudre ces problèmes, nous avons besoin d'une méthode flexible de synchronisation des threads. Les «verrous réentrants» sont cette méthode en Java qui offre une synchronisation bien plus flexible.
La classe «ReentrantLock» implémente les verrous réentrants et fait partie du package «import java.util.concurrent.locks». La classe ReentrantLock fournit la synchronisation de méthode pour accéder aux ressources partagées. Les classes ont également les méthodes de verrouillage et de déverrouillage pour verrouiller / déverrouiller les ressources lorsqu'elles y accèdent par des threads.
Une caractéristique particulière de ReentrantLock est que le thread peut verrouiller la ressource partagée plusieurs fois à l'aide de ReentrantLock. Il fournit un nombre de mises en attente qui est défini sur un lorsque le thread verrouille la ressource.
Le thread peut rentrer et accéder à la ressource avant de la déverrouiller. Chaque fois que le thread accède à la ressource à l'aide du verrou réentrant, le nombre de mises en attente est incrémenté de un. Pour chaque déverrouillage, le nombre de mises en attente est décrémenté de un.
Lorsque le nombre de mises en attente atteint 0, la ressource partagée est déverrouillée.
La classe ReentrantLock fournit également un paramètre d'équité qui est une valeur booléenne qui peut être transmise avec le constructeur du verrou. Lorsque le paramètre fairness est défini sur true, chaque fois qu'un thread libère le verrou, le verrou est transmis au thread en attente le plus long. Cela empêche la famine.
Les verrous réentrants peuvent être utilisés comme suit:
return_type method_name() { reentrantlock.lock(); try { //Do some work } catch(Exception e) { e.printStackTrace(); } finally { reentrantlock.unlock(); } }
Notez que l'instruction de déverrouillage pour ReentrantLock est toujours dans le bloc finally. Cela garantit que le verrou est libéré même si une exception est levée.
Implémentons un programme Java pour comprendre ReentrantLock.
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(); } }
Production
Dans le programme ci-dessus, nous avons créé un thread et utilisé ReentrantLock pour cela. En utilisant ReentrantLock, la ressource partagée est accessible.
Sémaphore en Java
La méthode suivante de synchronisation des threads consiste à utiliser Semaphore. En utilisant cette construction appelée sémaphore, l'accès à une ressource partagée est contrôlé par un compteur. Des signaux sont envoyés entre les threads afin que nous puissions garder la section critique et également éviter les signaux manqués.
Un sémaphore peut être défini comme une variable utilisée pour gérer des processus concurrents en synchronisant ces processus. Les sémaphores sont également utilisés pour synchroniser l'accès à la ressource partagée et ainsi éviter une condition de concurrence. L'autorisation donnée à un thread pour accéder à la ressource partagée par sémaphore est également appelée un permis.
Selon les fonctions qu'ils remplissent, les sémaphores peuvent être divisés en deux types:
# 1) Sémaphore binaire: Un sémaphore binaire est utilisé pour synchroniser les processus concurrents et implémenter l'exclusion mutuelle. Un sémaphore binaire ne prend que deux valeurs, à savoir 0 et 1.
# 2) Comptage du sémaphore: Le sémaphore de comptage a une valeur qui indique le nombre de processus pouvant entrer dans la section critique. À tout moment, la valeur indique le nombre maximum de processus qui entrent dans la section critique.
Alors, comment fonctionne un sémaphore?
Le fonctionnement d'un sémaphore peut être résumé dans les étapes suivantes:
- Si le nombre de sémaphores> 0, cela signifie que le thread a un permis pour accéder à la section critique, puis le nombre est décrémenté.
- Sinon, le thread est bloqué jusqu'à ce que l'autorisation soit acquise.
- Lorsque le thread a terminé d'accéder à la ressource partagée, l'autorisation est libérée et le nombre de sémaphores est incrémenté afin qu'un autre thread puisse répéter les étapes ci-dessus et acquérir l'autorisation.
Les étapes ci-dessus du fonctionnement des sémaphores peuvent être résumées dans l'organigramme ci-dessous.
En Java, nous n'avons pas besoin d'implémenter notre sémaphore mais il fournit un Sémaphore classe qui implémente la fonctionnalité de sémaphore. La classe Semaphore fait partie du java.util.concurrent paquet.
La classe Semaphore fournit les constructeurs suivants à l'aide desquels nous pouvons créer un objet sémaphore:
Semaphore (int num_value) Semaphore (int num_value, boolean how)
Ici,
num_value => valeur initiale du nombre d'autorisations qui détermine le nombre de threads pouvant accéder à la ressource partagée.
comment => définit l'ordre dans lequel les threads recevront les autorisations (how = true). Si how = false, aucun ordre de ce type n'est suivi.
Nous allons maintenant implémenter un programme Java qui démontrera le sémaphore utilisé pour gérer l'accès aux ressources partagées et éviter la condition de concurrence.
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. } }
Production
Ce programme a déclaré une classe pour la ressource partagée. Il déclare également une classe de thread dans laquelle nous avons une variable sémaphore initialisée dans le constructeur de classe.
Dans la méthode run () remplacée de la classe Thread, le traitement de l'instance de thread est effectué dans lequel le thread acquiert l'autorisation, accède à une ressource partagée, puis libère l'autorisation.
Dans la méthode principale, nous avons déclaré deux instances de thread. Les deux threads sont ensuite démarrés, puis ils attendent en utilisant la méthode de jointure. Enfin, le compte est affiché, c'est-à-dire 0 indiquant que les deux threads ont terminé avec la ressource partagée.
Fork et rejoindre en Java
Le framework fork / join a été introduit pour la première fois dans Java 7. Ce framework se compose d'outils qui peuvent accélérer le traitement parallèle. Il utilise tous les cœurs de processeur disponibles dans le système et termine la tâche. Le framework fork / join utilise l'approche divide and conquer.
L'idée de base derrière le framework Fork / Join est que le premier framework «Forks», c'est-à-dire divise récursivement la tâche en sous-tâches individuelles plus petites jusqu'à ce que les tâches soient atomiques afin qu'elles puissent être exécutées de manière asynchrone.
Après cela, les tâches sont «jointes», c'est-à-dire que toutes les sous-tâches sont jointes de manière récursive en une seule tâche ou valeur de retour.
Le framework fork / join a un pool de threads appelé «ForkJoinPool». Ce pool gère le type «ForkJoinWorkerThread» de threads de travail, fournissant ainsi un traitement parallèle efficace.
ForkJoinPool gère les threads de travail et nous aide également à obtenir des informations sur les performances et l'état du pool de threads. Le ForkJoinPool est une implémentation du «ExecutorService» dont nous avons discuté ci-dessus.
Contrairement aux threads de travail, ForkJoinPool ne crée pas de thread distinct pour chaque sous-tâche. Chaque thread de ForkJoinPool conserve son deque (file d'attente à deux extrémités) pour stocker les tâches.
Le deque agit comme un équilibrage de la charge de travail du thread et le fait à l'aide d'un «algorithme de vol de travail» décrit ci-dessous.
Algorithme de vol de travail
Nous pouvons définir l'algorithme de vol de travail en termes simples comme 'Si un thread est libre,' voler 'le travail des threads occupés'.
Un thread de travail obtiendra toujours les tâches de son deque. Lorsque toutes les tâches de la deque sont épuisées et que la deque est vide, le thread de travail prendra une tâche de la fin d’un autre deque ou de la «file d’entrée globale».
De cette façon, la possibilité que les threads se disputent des tâches est minimisée et le nombre de fois que le thread doit rechercher du travail est également réduit. C'est parce que le thread a déjà le plus gros travail disponible et l'a terminé.
Alors, comment pouvons-nous utiliser ForkJoinPool dans un programme?
La définition générale de ForkJoinPool est la suivante:
public class ForkJoinPool extends AbstractExecutorService
La classe ForkJoinPool fait partie du package «java.util.concurrent».
Dans Java 8, nous créons une instance de ForkJoinPool en utilisant sa méthode statique «common-pool ()» qui fournit une référence au pool commun ou au pool de threads par défaut.
ForkJoinPool commonPool = ForkJoinPool.commonPool ();
Dans Java 7, nous créons une instance de ForkJoinPool et l'attribuons au champ de la classe utilitaire comme indiqué ci-dessous.
public static ForkJoinPool forkJoinPool = new ForkJoinPool(2);
La définition ci-dessus indique que le pool a un niveau de parallélisme de 2 de sorte que le pool utilisera 2 cœurs de processeur.
Pour accéder à la piscine ci-dessus, nous pouvons donner la déclaration suivante.
ForkJoinPool forkJoinPool = PoolUtil.forkJoinPool;
Le type de base des tâches ForkJoinPool est «ForkJoinTask». Nous devrions étendre l'une de ses sous-classes, c'est-à-dire pour les tâches void, la RecursiveAction et pour les tâches renvoyant une valeur, la RecursiveTask. Les deux classes étendues fournissent une méthode abstraite compute () dans laquelle nous définissons la logique de la tâche.
Ci-dessous, un exemple illustrant le 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()); } }
Production
Dans le programme ci-dessus, nous trouvons le nombre de threads actifs dans le système avant et après l'appel de la méthode «invoke ()». La méthode invoke () est utilisée pour soumettre les tâches au pool. On retrouve également le nombre de cœurs de processeur disponibles dans le système.
Questions fréquemment posées
Q # 1) Qu'est-ce que Java Util Concurrent?
Répondre: Le package «java.util.concurrent» est un ensemble de classes et d'interfaces fournies par Java pour faciliter le développement d'applications simultanées (multi-threadées). En utilisant ce package, nous pouvons directement utiliser l'interface et les classes ainsi que les API sans avoir à écrire nos classes.
Q # 2) Lesquelles des implémentations suivantes sont des implémentations simultanées présentes dans java.util. package simultané?
Répondre: À un niveau élevé, le package java.util.concurrent contient des utilitaires tels que des exécuteurs, des synchroniseurs, des files d'attente, des minutages et des collections simultanées.
Q # 3) Qu'est-ce que Future Java?
Répondre: Un objet Future (java.util.concurrent.Future) est utilisé pour stocker le résultat renvoyé par un thread lorsque l'interface Callable est implémentée.
Q # 4) Qu'est-ce que thread-safe en Java?
Répondre: Un code ou une classe thread-safe en Java est un code ou une classe qui peut être partagé dans un environnement multithread ou concurrent sans aucun problème et produit les résultats attendus.
Q # 5) Qu'est-ce que la collection synchronisée en Java?
modèle de cascade de cycle de vie de développement logiciel
Répondre: Une collection synchronisée est une collection thread-safe. La méthode synchronized collection () de la classe java.util.Collections renvoie une collection synchronisée (thread-safe).
Conclusion
Avec ce tutoriel, nous avons terminé le sujet du multi-threading et de la concurrence en Java. Nous avons discuté du multithreading en détail dans nos précédents tutoriels. Ici, nous avons discuté de la concurrence et de l'implémentation liées à la concurrence et au multithreading qui font partie du package java.util.concurrent.
Nous avons discuté de deux autres méthodes de synchronisation, les sémaphores et ReentrantLock. Nous avons également discuté du ForkJoinPool qui est utilisé pour exécuter les tâches en les divisant en tâches plus simples, puis en joignant finalement le résultat.
Le package java.util.concurrent prend également en charge le framework Executor et les exécuteurs qui nous aident à exécuter des threads. Nous avons également discuté de l'implémentation du pool de threads qui se compose de threads réutilisables qui sont retournés au pool lorsque l'exécution est terminée.
Nous avons discuté d'une autre interface similaire à Runnable qui nous aide également à renvoyer un résultat du thread et de l'objet Future utilisé pour stocker le résultat du thread obtenu.
=> Regardez la série de formation Java simple ici.
lecture recommandée
- Thread.Sleep () - Méthode Thread Sleep () en Java avec des exemples
- Déploiement Java: création et exécution d'un fichier JAR Java
- Principes de base de Java: syntaxe Java, classe Java et principaux concepts Java
- Machine virtuelle Java: comment JVM aide à exécuter une application Java
- Modificateurs d'accès en Java - Tutoriel avec des exemples
- Java synchronisé: qu'est-ce que la synchronisation des threads en Java
- Tutoriel JAVA pour les débutants: plus de 100 tutoriels vidéo Java pratiques
- Classe Java Integer et Java BigInteger avec exemples