Nous savons tous qu'avant JDK1.5, lorsque la concurrence métier devait être implémentée en Java, les programmeurs devaient généralement réaliser l'implémentation du code de manière indépendante. Bien sûr, il existe certains frameworks open source qui fournissent ces fonctions, mais celles-ci ne sont toujours pas aussi utiles. comme les fonctions fournies avec JDK sont pratiques. Lors de la conception de programmes simultanés multithread Java de haute qualité, afin d'éviter des phénomènes tels que des sauts morts, tels que wait(), notify() et une synchronisation avant d'utiliser Java, il est souvent nécessaire de prendre en compte les performances, les blocages, l'équité, et les ressources. De nombreux facteurs tels que la gestion et la manière d'éviter les dommages causés par la sécurité des threads adoptent souvent des stratégies de sécurité plus complexes, ce qui augmente la charge de développement des programmeurs. Heureusement, après l'émergence du JDK1.5, Sun Master (Doug). Lea) a finalement introduit la boîte à outils java.util.concurrent pour simplifier l'achèvement simultané pour nous, pauvres petits programmeurs. Les développeurs peuvent utiliser cela pour réduire efficacement les conditions de concurrence et les threads de blocage. Le package concurrent résout très bien ces problèmes et nous fournit un modèle de programme concurrent plus pratique.
Exécuteur : l'exécuteur d'une tâche Runnable spécifique.
ExecutorService : Un gestionnaire de pool de threads, il existe de nombreuses classes d'implémentation, je vais en présenter quelques-unes. Nous pouvons soumettre Runnable et Callable au pool pour la planification.
Sémaphore : un sémaphore de comptage
ReentrantLock : un verrou réentrant mutuellement exclusif, dont la fonction est similaire à celle synchronisée, mais beaucoup plus puissant.
Future : Il s'agit d'une interface permettant d'interagir avec Runnable et Callable, comme obtenir le résultat renvoyé après l'exécution d'un thread, etc. Elle fournit également une annulation pour terminer le thread.
BlockingQueue : file d'attente de blocage.
CompletionService : extension d'ExecutorService, qui peut obtenir les résultats d'exécution du thread
CountDownLatch : classe d'assistance à la synchronisation qui permet à un ou plusieurs threads d'attendre qu'un ensemble d'opérations effectuées dans d'autres threads soit terminé.
CyclicBarrier : une classe d'assistance à la synchronisation qui permet à un groupe de threads de s'attendre jusqu'à ce qu'un point de barrière commun soit atteint
Future : Future représente le résultat d’un calcul asynchrone.
ScheduledExecutorService : un ExecutorService qui planifie l'exécution des commandes après un délai donné ou à intervalles réguliers.
Ensuite, nous les présenterons un par un
Description de la méthode principale des exécuteurs
newFixedThreadPool (pool de threads de taille fixe)
Créez un pool de threads capable de réutiliser un ensemble fixe de threads et exécutez ces threads dans une file d'attente illimitée partagée (seuls ceux qui sont demandés attendront dans une file d'attente pour exécution). Si un thread se termine en raison d'un échec lors de l'exécution avant l'arrêt, un nouveau thread effectuera les tâches suivantes à sa place (si nécessaire).
newCachedThreadPool (pool de threads illimité, peut effectuer un recyclage automatique des threads)
Crée un pool de threads qui crée de nouveaux threads selon les besoins, mais réutilise les threads précédemment construits dès qu'ils deviennent disponibles. Pour les programmes qui exécutent de nombreuses tâches asynchrones de courte durée, ces pools de threads améliorent souvent les performances du programme. L’appel d’execution réutilisera le thread précédemment construit (si le thread est disponible). Si aucun thread existant n'est disponible, un nouveau thread est créé et ajouté au pool. Terminez et supprimez du cache les threads qui n'ont pas été utilisés depuis 60 secondes. Par conséquent, un pool de threads qui reste inactif pendant une longue période n’utilisera aucune ressource. Notez que vous pouvez utiliser le constructeur ThreadPoolExecutor pour créer un pool de threads avec des propriétés similaires mais des détails différents (tels que les paramètres de délai d'attente).
newSingleThreadExecutor (un seul fil d'arrière-plan)
Créez un exécuteur qui utilise un seul thread de travail et exécute le thread dans une file d'attente illimitée. (Notez que si ce thread unique est terminé en raison d'un échec lors de l'exécution avant l'arrêt, un nouveau thread effectuera les tâches suivantes à sa place, si nécessaire). Les tâches sont garanties de s'exécuter de manière séquentielle et pas plus d'un thread ne sera actif à un moment donné. Contrairement à l'équivalent newFixedThreadPool(1), l'exécuteur renvoyé par cette méthode est assuré de pouvoir utiliser d'autres threads sans le reconfigurer.
Ces méthodes renvoient des objets ExecutorService, qui peuvent être compris comme un pool de threads.
La fonction de ce pool de threads est relativement complète. Vous pouvez soumettre des tâches avec submit() et terminer le pool de threads avec shutdown().
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;classe publique MyExecutor extends Thread {private int index;public MyExecutor(int i){ this.index=i;}public void run(){ try{ System.out.println("["+this.index+"] start...."); Thread.sleep((int)(Math.random()*)); System.out.println("["+this.index+"] end." } catch(Exception e){ e.printStackTrace(); }}public static void main(String args[]){ ExecutorService service=Executors.newFixedThreadPool(); for(int i=;i<;i++){ service.execute(new MyExecutor(i)); //service.submit(new MyExecutor(i)); } System.out.println("submit finish");
Bien que certaines informations soient imprimées, le fonctionnement de ce pool de threads n'est pas très clair. Augmentons le temps de veille de 10 fois.
Thread.sleep((int)(Math.random()*10000));
En regardant plus loin, vous verrez clairement que seuls 4 threads peuvent être exécutés. Lorsqu'un thread est exécuté, un nouveau thread sera exécuté. C'est-à-dire qu'après avoir soumis tous les threads, le pool de threads attendra l'arrêt final pour être exécuté. Nous constaterons également que le fil de soumission est placé dans une « file d’attente illimitée ». Il s'agit d'une file d'attente ordonnée (BlockingQueue, qui sera discutée ci-dessous).
De plus, il utilise la fonction statique des exécuteurs pour générer un pool de threads fixe. Comme son nom l'indique, le thread du pool de threads ne sera pas libéré, même s'il est inactif.
Cela entraînera des problèmes de performances. Par exemple, si la taille du pool de threads est de 200, lorsque tous les threads sont utilisés, tous les threads continueront à rester dans le pool, ainsi que la mémoire et la commutation de threads correspondantes (while(true)+sleep loop). ) va augmenter.
Si vous souhaitez éviter ce problème, vous devez utiliser ThreadPoolExecutor() directement pour le construire. Vous pouvez définir le « nombre maximum de threads », le « nombre minimum de threads » et la « durée de conservation des threads inactifs » comme un pool de threads général.
Il s’agit de l’utilisation de base du pool de threads.
Sémaphore
Un sémaphore de comptage. Conceptuellement, un sémaphore gère un ensemble d'autorisations. Si nécessaire, chaque acquire() est bloqué jusqu'à ce que l'autorisation soit disponible, puis l'autorisation est acquise. Chaque release() ajoute une autorisation, libérant potentiellement un acquéreur bloquant. Cependant, au lieu d'utiliser de véritables objets de licence, Semaphore compte simplement le nombre de licences disponibles et prend les mesures en conséquence.
Le sémaphore est souvent utilisé pour limiter le nombre de threads pouvant accéder à certaines ressources (physiques ou logiques). Par exemple, la classe suivante utilise des sémaphores pour contrôler l'accès à un pool de contenu :
Voici une situation réelle : tout le monde fait la queue pour aller aux toilettes. Il n'y a que deux places dans les toilettes. Lorsque 10 personnes arrivent, elles doivent faire la queue.
importer java.util.concurrent.ExecutorService; importer java.util.concurrent.Executors; importer java.util.concurrent.Semaphore; classe publique MySemaphore étend le fil {Position du sémaphore; identifiant int privé; public MySemaphore (int i, Sémaphore s) { this.id=i; this.position=s;}public void run(){ essayer{ if(position.availablePermits()>){ System.out.println("Client["+this.id+"] entre dans les toilettes, il y a de la place" } else{ System.out.println("Client["+); this. id+"] entre dans les toilettes, pas d'espace, file d'attente"); } position.acquire(); System.out.println("Le client ["+this.id+"] acquiert un siège dans la fosse"); Thread.sleep((int)(Math.random()*)); System.out.println("Le client ["+this.id+"] a fini d'utiliser"); ) { e.printStackTrace(); }} public static void main(String args[]){ ExecutorService list=Executors.newCachedThreadPool(); Sémaphore(); for(int i=;i<;i++){ list.submit(new MySemaphore(i+,position)); } list.shutdown(); Terminé, il faut nettoyer"); position.release();}}
Verrouillage réentrant
Un verrou mutex réentrant qui présente certains des mêmes comportements et sémantiques de base que le verrou de moniteur implicite accessible à l'aide de méthodes et d'instructions synchronisées, mais qui est plus puissant.
Un ReentrantLock appartiendra au thread qui a acquis le verrou avec succès le plus récemment et qui n’a pas encore libéré le verrou. Lorsque le verrou n'appartient pas à un autre thread, le thread appelant le verrou acquerra avec succès le verrou et reviendra. Si le thread actuel détient déjà le verrou, cette méthode est renvoyée immédiatement. Vous pouvez utiliser les méthodes isHeldByCurrentThread() et getHoldCount() pour vérifier si cela se produit.
Le constructeur de cette classe accepte un paramètre d'équité facultatif.
Lorsqu'ils sont définis sur true, en cas de conflit entre plusieurs threads, ces verrous ont tendance à accorder l'accès au thread qui a attendu le plus longtemps. Dans le cas contraire, ce verrou ne garantira aucun ordre d'accès spécifique.
Par rapport au paramètre par défaut (utilisation du verrouillage injuste), un programme utilisant le verrouillage équitable aura un débit global très faible (c'est-à-dire qu'il sera très lent, souvent extrêmement lent) lorsqu'il sera accédé par de nombreux threads, mais aura de mauvaises performances dans l'acquisition des verrous. et garantir l'allocation des verrous. La différence est minime en termes d'équilibre.
Cependant, il convient de noter que le verrouillage équitable ne garantit pas l’équité de la planification des threads. Par conséquent, l'un des nombreux threads utilisant un verrou équitable peut avoir plusieurs fois plus de chances de succès, ce qui se produit lorsque d'autres threads actifs ne sont pas en cours de traitement et ne détiennent pas actuellement le verrou.
Notez également que la méthode tryLock non chronométrée n'utilise pas les paramètres d'équité. Parce que cette méthode peut réussir tant que le verrou est disponible même si d'autres threads attendent.
Il est recommandé de toujours s'entraîner immédiatement et d'utiliser un bloc try pour appeler le verrouillage. Dans la construction avant/après, le code le plus typique est le suivant :
class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // bloquer jusqu'à ce que la condition soit vérifiée { // ... corps de la méthode } enfin { lock. déverrouiller() } }}
Mon exemple :
importer java.util.concurrent.ExecutorService; importer java.util.concurrent.Executors; importer java.util.concurrent.locks.ReentrantLock; classe publique MyReentrantLock étend le fil {verrouillage TestReentrantLock; identifiant int privé; public MyReentrantLock (int i, test TestReentrantLock ){ this.id=i; this.lock=test;}public void run(){ lock.print(id);} public static void main(String args[]){ ExecutorService service=Executors.newCachedThreadPool(); TestReentrantLock lock=new TestReentrantLock(); for(int i=;i<;i++){ service. submit(new MyReentrantLock(i,lock)); } service.shutdown();}}class TestReentrantLock{privé ReentrantLock lock=new ReentrantLock(); public void print(int str){ try{ lock.lock(); System.out.println(str+"get"); )); } catch(Exception e){ e.printStackTrace(); } enfin{ System.out.println(str+"release");
File d'attente de blocage
Une file d'attente qui prend en charge deux opérations supplémentaires : attendre que la file d'attente ne soit pas vide lors de la récupération d'un élément et attendre que de l'espace soit disponible lors du stockage d'un élément.
BlockingQueue n'accepte pas les éléments nuls. Certaines implémentations lèvent NullPointerException lorsqu'elles tentent d'ajouter, de mettre ou de proposer un élément nul. null est utilisé comme valeur d'avertissement pour indiquer que l'opération d'interrogation a échoué.
BlockingQueue peut être limité en capacité. Il peut avoir une capacité restante à tout instant, au-delà de laquelle il ne peut pas mettre d'éléments supplémentaires sans bloquer.
Un BlockingQueue sans aucune contrainte de capacité interne signale toujours Integer.MAX_VALUE de la capacité restante.
L'implémentation BlockingQueue est principalement utilisée pour les files d'attente producteur-consommateur, mais elle prend également en charge l'interface Collection. Ainsi, par exemple, il est possible de supprimer un élément arbitraire de la file d'attente en utilisant Remove(x).
Cependant, cette opération n'est généralement pas effectuée de manière efficace et ne peut être utilisée qu'occasionnellement et de manière planifiée, par exemple lors du retrait d'un message de la file d'attente.
L'implémentation de BlockingQueue est thread-safe. Toutes les méthodes de mise en file d'attente peuvent utiliser un verrouillage interne ou d'autres formes de contrôle de concurrence pour atteindre automatiquement leurs objectifs.
Cependant, un grand nombre d'opérations de collection (addAll, containAll, retentionAll et removeAll) ne sont pas nécessairement effectuées automatiquement, sauf indication contraire dans l'implémentation.
Ainsi, par exemple, addAll(c) peut échouer (lancer une exception) après avoir ajouté seulement quelques éléments dans c.
BlockingQueue ne prend essentiellement en charge aucun type d'opération de « fermeture » ou « d'arrêt » pour indiquer qu'aucun élément ne sera ajouté.
Le besoin et l’utilisation de cette fonctionnalité dépendent généralement de la mise en œuvre. Par exemple, une stratégie courante consiste à insérer des objets spéciaux de fin de flux ou empoisonnés dans le producteur et à les interpréter en fonction du moment où le consommateur les reçoit.
L'exemple suivant illustre les fonctionnalités de base de cette file d'attente de blocage.
importer java.util.concurrent.BlockingQueue; importer java.util.concurrent.ExecutorService; importer java.util.concurrent.Executors; importer java.util.concurrent.LinkedBlockingQueue; classe publique MyBlockingQueue étend Thread {file d'attente statique publique BlockingQueue<String> = new LinkedBlockingQueue<String>();index int privé;public MyBlockingQueue(int i) { this.index = i;} public void run() { try { queue.put(String.valueOf(this.index)); System.out.println("{" + this.index + " } dans la file d'attente!"); } catch (Exception e) { e.printStackTrace(); }} public static void main(String args[]) { ExecutorService service = Executors.newCachedThreadPool(); for (int i = ; i < ; i++) { service.submit(new MyBlockingQueue(i)); } Thread thread = new Thread() { public void run() { try { while (true) { Thread.sleep((int) (Math.random() * )); if(MyBlockingQueue.queue.isEmpty()) break; MyBlockingQueue.queue.take(); System.out.println(str + " has take!" } } catch (Exception e) { e.printStackTrace(); service.submit(thread service); fermer();}}
--------------------------Résultats d'exécution-----------------
{0} en file d'attente !
{1} dans la file d'attente !
{2} dans la file d'attente !
{3} dans la file d'attente !
0 a pris !
{4} dans la file d'attente !
1 a pris !
{6} dans la file d'attente !
2 a pris !
{7} dans la file d'attente !
3 a pris !
{8} en file d'attente !
4 a pris !
{5} en file d'attente !
6 a pris !
{9} dans la file d'attente !
7 a pris !
8 a pris !
5 a pris !
9 a pris !
---------------------------------------------
Service d'achèvement
Un service qui sépare la production de nouvelles tâches asynchrones de la consommation des résultats des tâches terminées. Le producteur soumet la tâche à accomplir. L'utilisateur prend les tâches terminées et traite leurs résultats dans l'ordre dans lequel elles ont été terminées. Par exemple, un CompletionService peut être utilisé pour gérer les E/S asynchrones. La tâche d'exécution d'une opération de lecture est soumise dans le cadre du programme ou du système. Ensuite, lorsque l'opération de lecture est terminée, d'autres opérations sont effectuées dans une autre partie du programme. , éventuellement dans l'ordre dans lequel les opérations ont été demandées. L'ordre est différent.
En règle générale, un CompletionService s'appuie sur un exécuteur distinct pour effectuer réellement la tâche, auquel cas CompletionService gère uniquement une file d'attente d'achèvement interne. La classe ExecutorCompletionService fournit une implémentation de cette méthode.
importer java.util.concurrent.Callable; importer java.util.concurrent.CompletionService; importer java.util.concurrent.ExecutorCompletionService; importer java.util.concurrent.ExecutorService; importer java.util.concurrent.Executors; classe publique MyCompletionService implémente Callable <String> {identifiant int privé; public MyCompletionService (int i){ this.id=i;}public static void main(String[] args) throws Exception{ ExecutorService service=Executors.newCachedThreadPool(); i<;i++){ complétion.submit(new MyCompletionService(i)); } for(int i=;i<;i++){ System.out.println(completion.take().get()); } service.shutdown();} public String call() throws Exception { Integer time=(int)(Math.random()* try{); System.out.println(this.id+" start"); Thread.sleep(time); System.out.println(this.id+" end"); e.printStackTrace(); } return this.id+":"+time;}}
Compte à rebours
Classe d'assistance à la synchronisation qui permet à un ou plusieurs threads d'attendre qu'un ensemble d'opérations effectuées dans d'autres threads soit terminé.
Initialise CountDownLatch avec le nombre donné. Étant donné que la méthode countDown() est appelée, la méthode wait se bloque jusqu'à ce que le décompte actuel atteigne zéro.
Ensuite, tous les threads en attente sont libérés et tous les appels suivants à wait reviennent immédiatement. Ce comportement ne se produit qu'une seule fois : le décompte ne peut pas être réinitialisé. Si vous devez réinitialiser le décompte, pensez à utiliser CyclicBarrier.
CountDownLatch est un outil de synchronisation général qui a de nombreuses utilisations. Utilisez un CountDownLatch initialisé avec le compte 1 comme simple verrou marche/arrêt, ou entrée : tous les threads appelant wait attendent à l'entrée jusqu'à ce que l'entrée soit ouverte par le thread appelant countDown().
Un CountDownLatch initialisé avec N peut amener un thread à attendre que N threads aient terminé une opération, ou à attendre qu'une opération soit terminée N fois.
Une fonctionnalité utile de CountDownLatch est qu'il n'exige pas que le thread appelant la méthode countDown attende que le compte atteigne zéro avant de continuer, mais empêche plutôt tout thread de continuer via une attente jusqu'à ce que tous les threads puissent passer.
L'exemple ci-dessous a été écrit par quelqu'un d'autre et est très frappant.
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestCountDownLatch {public static void main(String[] args) throws InterruptedException { // Démarrage du verrouillage final du compte à rebours CountDownLatch start = new CountDownLatch(); // Fin du verrouillage du compte à rebours final CountDownLatch end = new CountDownLatch(); // Dix candidats finaux ExecutorService exec = Executors.newFixedThreadPool(); for (int index = ; index < ; index++) { final int NO = index + ; Runnable run = new Runnable() { public void run( ) { try { start.await();//toujours bloquant Thread.sleep((long) (Math.random() * )); System.out.println("Non." + NON + "arrivé"); } catch (InterruptedException e) { } enfin { end.countDown( } } }; println("Début du jeu"); start.countDown(); end.await(); System.out.println("Game Over");
Les méthodes les plus importantes de CountDownLatch sont countDown() et wait(). La première compte à rebours principalement une fois, et la seconde attend que le compte à rebours arrive à 0. S'il n'atteint pas 0, il ne fera que bloquer et attendre.
Barrière cyclique
Une classe d'aide à la synchronisation qui permet à un groupe de threads de s'attendre jusqu'à ce qu'un point de barrière commun soit atteint.
CyclicBarrier est utile dans les programmes impliquant un ensemble de threads de taille fixe qui doivent s'attendre de temps en temps. Étant donné que la barrière peut être réutilisée une fois le thread en attente libéré, on l'appelle une barrière cyclique.
CyclicBarrier prend en charge une commande Runnable facultative qui n'est exécutée qu'une seule fois à chaque point de barrière, après l'arrivée du dernier thread d'un ensemble de threads (mais avant que tous les threads ne soient libérés). Cette opération de barrière est utile lors de la mise à jour de l’état partagé avant de continuer tous les threads participants.
Exemple d'utilisation : ce qui suit est un exemple d'utilisation d'une barrière dans une conception de décomposition parallèle, un exemple de groupe de tournée très classique :
importer java.text.SimpleDateFormat; importer java.util.Date; importer java.util.concurrent.BrokenBarrierException; importer java.util.concurrent.CyclicBarrier; importer java.util.concurrent.ExecutorService; importer java.util.concurrent.Executors; public class TestCyclicBarrier { // Temps requis pour la randonnée : Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhan privé statique int[] timeWalk = { , , , , , }; // Visite autonome privée statique int[] timeSelf = { , , , , }; // Visite en bus privé statique int[] timeBus = { , , , , } ; chaîne statique maintenant() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); sdf.format(new Date()) + ": "; } classe statique Tour implémente Runnable { private int[] times; private String tourName; public Tour(CyclicBarrier Barrier, String tourName, int[] times) { this.times = times; this.tourName = tourName; this.barrier = barrière; } public void run() { try { Thread.sleep(times[] * ); System.out.println(now() + tourName + " A atteint Shenzhen"); barrière.await(); Thread.sleep(times[] * ); System.out.println(now() + tourName + " A atteint Guangzhou" ); barrière.await(); Thread.sleep(times[] * ); System.out.println(now() + tourName + " A atteint Shaoguan"); barrière.await(); Thread.sleep(times[] * ); System.out.println(now() + tourName + " A atteint Changsha"); Thread.sleep(times[] * ); System.out.println(now() + tourName + " Atteint Wuhan"); barrière.await(); } catch (InterruptedException e) { } catch (BrokenBarrierException e) { } } } public static void main(String[] args) { // Trois groupes de visites CyclicBarrier Barrier = new CyclicBarrier(); "WalkTour", timeWalk)); exec.submit(nouveau Tour(barrière, "SelfTour", timeSelf));//Lorsque nous commentons le code suivant, nous constaterons que le programme est bloqué et ne peut pas continuer à s'exécuter. exec.submit(new Tour(barrière, "BusTour", timeBus)); exec.shutdown();
L'attribut le plus important de CyclicBarrier est le nombre de participants, et la méthode la plus importante est wait(). Lorsque tous les threads appellent wait(), cela signifie que ces threads peuvent continuer à s'exécuter, sinon ils attendront.
Avenir
Future représente le résultat d'un calcul asynchrone. Il fournit des méthodes pour vérifier si le calcul est terminé, attendre la fin du calcul et récupérer le résultat du calcul.
Seule la méthode get peut être utilisée pour récupérer les résultats une fois le calcul terminé. Si nécessaire, cette méthode peut être bloquée avant la fin du calcul. L'annulation est effectuée par la méthode Cancel.
Des méthodes supplémentaires sont fournies pour déterminer si une tâche s'est terminée normalement ou a été annulée. Une fois un calcul terminé, il ne peut plus être annulé.
Si vous utilisez un Future pour l'annulation mais que vous ne fournissez pas de résultat utilisable, vous pouvez déclarer un type formel Future<?> et renvoyer null comme résultat de la tâche sous-jacente.
Nous avons vu cela plus tôt dans CompletionService, la fonction de ce Future, et cela peut être spécifié comme objet de retour lors de la soumission du fil.
Service d'exécution planifié
ExecutorService qui planifie l'exécution des commandes après un délai donné ou à intervalles réguliers.
La méthode planning crée des tâches avec différents retards et renvoie un objet tâche qui peut être utilisé pour annuler ou vérifier l'exécution. Les méthodes ScheduleAtFixedRate et ScheduleWithFixedDelay créent et exécutent certaines tâches qui s'exécutent périodiquement jusqu'à leur annulation.
Les commandes soumises à l'aide d'Executor.execute(java.lang.Runnable) et de la méthode de soumission d'ExecutorService sont planifiées avec le délai demandé de 0.
Les délais nuls et négatifs (mais pas les périodes) sont autorisés dans la méthode de planification, et ceux-ci sont traités comme des demandes à exécuter immédiatement.
Toutes les méthodes de planification acceptent des délais et des périodes relatifs comme paramètres, plutôt que des heures ou des dates absolues. Il est facile de convertir l’heure absolue représentée par une date sous la forme requise.
Par exemple, pour planifier une exécution à une date ultérieure, utilisez : planning(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS).
Notez cependant qu'en raison des protocoles de synchronisation de l'heure du réseau, de la dérive de l'horloge ou d'autres facteurs, la date d'expiration relativement retardée ne doit pas nécessairement correspondre à la date actuelle de la tâche activée.
La classe Executors fournit des méthodes de fabrique pratiques pour l'implémentation de ScheduledExecutorService fournie dans ce package.
Les exemples suivants sont également populaires sur Internet.
importer java.util.concurrent.TimeUnit.SECONDS statique; importer java.util.Date; importer java.util.concurrent.Executors; importer java.util.concurrent.ScheduledExecutorService; importer java.util.concurrent.ScheduledFuture; classe publique TestScheduledThread { public static void main (String[] args) { planificateur final ScheduledExecutorService = Executors.newScheduledThreadPool(); final Runnable beeper = new Runnable() { int count = ; public void run() { System.out.println(new Date() + " beep " + (++count)); // Exécuté après quelques secondes et toutes les secondes finales ScheduledFuture beeperHandle = planning.scheduleAtFixedRate(beeper, , , SECONDS); // Exécuter après quelques secondes et attendre quelques secondes après la fin de l'exécution de la dernière tâche, puis réexécuter à chaque fois final ScheduledFuture beeperHandle = planning.scheduleWithFixedDelay(beeper, , , SECONDS); // Termine la tâche après quelques secondes et ferme le planificateur. . planning(new Runnable() { public void run() { beeperHandle.cancel(true); beeperHandle.cancel(true); planificateur.shutdown(); } }, , SECONDES);}}
De cette façon, nous avons résumé les fonctions les plus importantes du package concurrent. Nous espérons que cela sera utile à notre compréhension.