2. Introduction
La technologie multithreading résout principalement le problème de l'exécution de threads multiples dans une unité de processeur. Il peut réduire considérablement le temps d'inactivité de l'unité de processeur et augmenter la capacité de débit de l'unité de processeur. Cependant, les frais généraux de la création de threads fréquents sont très importants. Alors, comment réduire cette partie des frais généraux, vous devez envisager d'utiliser un pool de fils. Un pool de threads est un conteneur de thread, qui exécute uniquement un nombre nominal de threads à la fois. Le pool de threads est utilisé pour gérer ce nombre nominal de threads.
3. Diagramme de structure de classe impliquant une piscine de fil
Parmi eux, le principal à utiliser est la classe ThreadPoolExecutor.
4. Comment créer un pool de fils
Nous avons généralement les méthodes suivantes pour créer des pools de threads:
1. Utilisez la classe d'usine des exécuteurs
Les exécuteurs fournissent principalement les méthodes suivantes pour créer des pools de threads:
Jetons un coup d'œil à l'exemple d'utilisation ci-dessous:
1) NewFixEdThreadpool (Pool de fil fixe)
classe publique fixeThreadpool {public static void main (String [] args) {exécutorService pool = exécutor.NewFixEdThreadpool (5); // Créer un pool de threads avec une taille fixe de 5 pour (int i = 0; i <10; i ++) {pool.submit (new Mythread ()); } pool.shutdown (); }} public class Mythread étend Thread {@Override public void run () {System.out.println (thread.currentThread (). getName () + "Execution ..."); }}Les résultats des tests sont les suivants:
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-2 s'exécute. . .
Pool-1-thread-3 s'exécute. . .
Pool-1-thread-2 s'exécute. . .
Pool-1-thread-3 s'exécute. . .
Pool-1-thread-2 s'exécute. . .
Pool-1-thread-2 s'exécute. . .
Pool-1-thread-3 s'exécute. . .
Pool-1-thread-5 est en cours d'exécution. . .
Pool-1-thread-4 s'exécute. . .
Pool de threads de taille fixe: Créez un thread chaque fois qu'une tâche est soumise, jusqu'à ce que le thread atteigne la taille maximale du pool de threads. La taille de la piscine de thread restera inchangée une fois qu'elle atteindra sa valeur maximale. Si un thread se termine en raison d'une exception d'exécution, le pool de threads ajoutera un nouveau thread.
2) NewsingLeThreAdExecutor (Pool à fil unique)
classe publique SingleThreadpool {public static void main (String [] args) {exécutorService pool = exécutors.newSingLethreAdExEcutor (); // Créer un seul pool de threads pour (int i = 0; i <100; i ++) {pool.submit (new myThread ()); } pool.shutdown (); }}Les résultats des tests sont les suivants:
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
Pool de threads à thread unique: ce pool de threads n'a qu'un seul fil qui fonctionne, ce qui signifie qu'une exécution en série unique de toutes les tâches. Si ce fil unique se termine en raison de l'exception, il y aura un nouveau fil pour le remplacer. Ce pool de threads garantit que l'ordre d'exécution de toutes les tâches est exécuté dans l'ordre de la soumission de tâche.
3) NewscheduledThreadpool
classe publique ScheduledThreadPool {public static void main (String [] args) {planifié de pool de services de service = exécutor pour (int i = 0; i <10000; i ++) {pool.submit (new Mythread ()); } pool.schedule (new Mythread (), 1000, timeunit.milliseconds); Pool.Schedule (new Mythread (), 1000, timeUnit.Millisecondes); pool.shutdown (); }}Les résultats des tests sont les suivants:
Pool-1-thread-1 s'exécute. . .
Pool-1-thread-6 s'exécute. . .
Pool-1-thread-5 est en cours d'exécution. . .
Pool-1-thread-4 s'exécute. . .
Pool-1-thread-2 s'exécute. . .
Pool-1-thread-3 s'exécute. . .
Pool-1-thread-4 s'exécute. . .
Pool-1-thread-5 est en cours d'exécution. . .
Pool-1-thread-6 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
……………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………….
Pool-1-thread-4 s'exécute. . .
Pool-1-thread-1 s'exécute. . .
Les deux derniers threads du résultat du test ne commencent à s'exécuter qu'après avoir retardé 1s. Ce pool de thread prend en charge l'exigence de synchronisation et d'exécution périodique des tâches
4) NewCachedThreadpool (piscine en fil mitoire)
classe publique CachedThreadPool {public static void main (String [] args) {EMMIRCESERVICE POOL = EMICATORS.NEWCACHEDTHERRADPOOL (); pour (int i = 0; i <100; i ++) {pool.submit (new Mythread ()); } pool.shutdown (); }}Les résultats des tests sont les suivants:
Pool-1-thread-5 est en cours d'exécution. . .
Pool-1-thread-7 s'exécute. . .
Pool-1-thread-5 est en cours d'exécution. . .
Pool-1-thread-16 est en exécution. . .
Pool-1-thread-17 s'exécute. . .
Pool-1-thread-16 est en exécution. . .
Pool-1-thread-5 est en cours d'exécution. . .
Pool-1-thread-7 s'exécute. . .
Pool-1-thread-16 est en exécution. . .
Pool-1-thread-18 s'exécute. . .
Pool-1-thread-10 s'exécute. . .
Pool de threads cacheable: Si la taille du pool de thread dépasse le thread requis pour traiter la tâche, certains threads inactifs (aucune exécution de tâche en 60 secondes) seront recyclés. Lorsque le nombre de tâches augmente, ce pool de fils peut ajouter intelligemment de nouveaux threads pour gérer la tâche. Ce pool de thread ne limite pas la taille du pool de threads, ce qui dépend entièrement de la taille maximale du thread que le système de fonctionnement (ou JVM) peut créer.
Le responsable suggère que les programmeurs utilisent les exécuteurs des méthodes d'usine les plus pratiques. Ces pools de threads sont prédéfinis par défaut de configuration pour la plupart des scénarios d'utilisation.
2. Hériter de la classe ThreadPoolExecutor et copier la méthode du constructeur de la classe parent.
Avant d'introduire cette méthode, analysons les quelques codes sous-jacents précédents pour la création de pools de threads?
Exécuteurs de classe publique {public static exécutorService newFixEdThreadpool (int nThreads) {return new ThreadPoolExeCutor (nthreads, nthreads, 0l, timeunit.milliseconds, new LinkedBlockerEue <Runnable> ()); } public static ExecutorService NewsingLetHreAdExecutor () {return new FinaliseableDelegatedExecutService (new ThreadPoolExecutor (1, 1, 0l, timeunit.milliseconds, new LinkedBlockeue <Runnable> ())); }}À partir du code sous-jacent de la classe d'usine des exécuteurs, nous pouvons voir que les méthodes fournies par la classe d'usine pour créer des pools de threads sont réellement implémentées en construisant ThreadPoolExecutor. Le code de méthode du constructeur ThreadPoolExecutor est le suivant:
public threadpoolExecutor (int corepoolSize, int maximumpoolSize, long keepalivetime, timeunit unité, blocingQueue <runnable> workqueue, threadfactory threadfactory, rejectEdExecutionHandler mandepre IllégalArgumentException (); if (workQueue == null || threadfactory == null || handler == null) lance un nouveau nullpointerException (); this.CorepoolSize = corePoolSize; this.MaxiMumpoolSize = maximumpoolSize; this.workQueue = workQueue; this.keepaliveTime = unit.Tonanos (keepalivetime); this.threadfactory = threadFactory; this.handler = handler; }
Parlons ensuite de la méthode du constructeur ThreadPoolExecutor. Dans cette méthode de construction, il existe principalement les paramètres suivants:
CorePoolSize - le nombre de threads enregistrés dans la piscine, y compris les threads gratuits.
MaximumpoolSize - Le nombre maximum de fils autorisés dans la piscine.
KeepaliveTime - Lorsque le nombre de threads est supérieur à CorePoolSize, c'est le plus longtemps pour que le fil inactif attende une nouvelle tâche.
Unité - GentiSaliveTime Paramètre Time Unit.
WorkQueue - La file d'attente utilisée pour conserver les tâches avant l'exécution. Cette file d'attente maintient uniquement les tâches exécutables soumises par la méthode d'exécution.
ThreadFactory - L'usine utilisée par les exécuteurs pour créer de nouveaux threads.
Handleur - Le gestionnaire utilisé lorsque l'exécution est bloquée en raison de la portée du fil et de la capacité de file d'attente.
Ensuite, parlons de la relation entre ces paramètres. Lorsque le pool de threads vient d'être créé, il n'y a pas de threads dans le pool de threads (notez qu'il n'est pas qu'un certain nombre de threads sont créés dès que le pool de threads est créé). Lorsque la méthode EXECUTE () est appelée pour ajouter une tâche, le pool de threads fera le jugement suivant:
1) Si le nombre de threads en cours d'exécution est inférieur à CorePoolSize, créez immédiatement un nouveau thread pour effectuer cette tâche.
2) Si le nombre de threads en cours d'exécution est supérieur ou égal à CorePoolSize, cette tâche sera mise dans la file d'attente.
3) Si la file d'attente du pool de threads est pleine, mais le nombre de threads en cours d'exécution est inférieur à MaximumpoolSize, un nouveau thread sera toujours créé pour effectuer cette tâche.
4) Si la file d'attente est pleine et que le nombre de threads en cours d'exécution est supérieur ou égal à MaximumpoolSize, le pool de threads gérera la tâche actuelle en fonction de la politique de rejet.
5) Lorsqu'une tâche est exécutée, le thread prendra la tâche suivante de la file d'attente à exécuter. S'il n'y a pas de tâche à exécuter dans la file d'attente, le thread sera inactif. Si le temps de survie de KeepaliveTime dépasse, le fil sera recyclé par le pool de threads (Remarque: Les threads de recyclage sont conditionnels. Si le nombre de threads en cours d'exécution est supérieur à Corepoolsize, le fil sera détruit. S'il n'est pas supérieur à CorepoolSize, le thread ne sera pas détruit. Pourquoi n'est-ce pas que le fil soit recyclé dès qu'il est inactif, mais qu'il doit attendre qu'il dépasse le gardien de gardien avant que le fil ne soit recyclé? La raison est très simple: parce que la création et la destruction des fils consomment beaucoup, et il ne peut pas être fréquemment créé et détruit. Après avoir dépassé Keepalivetime, on constate que ce fil n'est en effet pas utilisé et qu'il sera détruit. Dans ce cas, l'unité représente l'unité de temps de KeepaliveTime, et la définition de l'unité est la suivante:
Public Enum TimeUnit {nanoseconds {// keepalivetime in nanoseconds}, microsecondes {// keepalivetime in microseconds}, millisecondes {// keepalivetime in milliseconds}, seconds {// keepalivetime in secondes}, minutes {// keepalineI temps keepalivetime en heures}, jours {// keepalivetime en jours}; Analyons le code source ci-dessous. Pour les situations ci-dessus, les codes source principalement impliqués sont les suivants:
Boolean privé AddiFunderCorePoolSize (Runnable FirstTask) {Thread T = null; rentrantlock final mainlock = this.mainlock; mainlock.lock (); essayez {if (poolSize <corePoolSize && runState == running) t = addThread (firstTask); } enfin {mainlock.unlock (); } if (t == null) return false; t.start (); Retour Vrai; } En fait, ce code est très simple. Il décrit principalement que si le pool de threads actuel est plus petit que CorePoolSize, un nouveau thread est créé pour gérer la tâche.
Boolean privé addifunderMaxiMumpoolSize (Runnable FirstTask) {Thread t = null; rentrantlock final mainlock = this.mainlock; mainlock.lock (); essayez {if (poolSize <maximumpoolSize && runState == Running) t = addThread (firstTask); } enfin {mainlock.unlock (); } if (t == null) return false; t.start (); Retour Vrai; }Le code ci-dessus décrit que si le nombre de pools de threads actuels est inférieur à MaximumpoolSize, un thread sera créé pour exécuter la tâche.
5. La file d'attente de la piscine de fil
Il existe 3 types de files d'attente de piscine:
Commit direct: L'option par défaut pour les files d'attente de travail est SynchronousQueue, qui soumet directement les tâches aux threads sans les garder. Ici, s'il n'y a pas de thread disponible pour exécuter la tâche immédiatement, tenter de faire la queue, la tâche échouera, construisant ainsi un nouveau thread. Cette politique évite les verrous lors du traitement des ensembles de demandes qui peuvent avoir des dépendances internes. Les soumissions directes nécessitent généralement une maximum de maximum illimité pour éviter de rejeter les tâches nouvellement soumises. Cette stratégie permet aux fils illimités d'avoir la possibilité de croissance lorsque les commandes arrivent en continu avec une moyenne que la file d'attente peut gérer.
Fitre illimité: l'utilisation d'une file d'attente non liée (par exemple, LinkedBlockingQueue sans capacité prédéfinie) fera attendre de nouvelles tâches dans la file d'attente lorsque tous les threads de base sont occupés. De cette façon, le thread créé ne dépassera pas CorePoolSize. (La valeur de MaximumpoolSize n'est pas valide.) Lorsque chaque tâche est complètement indépendante des autres tâches, c'est-à-dire que l'exécution de la tâche ne s'affiche pas, elle convient aux files d'attente illimitées; Par exemple, dans un serveur de pages Web. Cette file d'attente peut être utilisée pour gérer les demandes de rafale transitoires, et cette stratégie permet aux threads illimités d'avoir la possibilité de croissance lorsque les commandes arrivent en continu dépassant la moyenne que la file d'attente peut gérer.
Fitre bornée: lors de l'utilisation de maximumumpo-sacles limitées, les files d'attente limitées (telles que ArrayBlockingQueue) aident à prévenir l'épuisement des ressources, mais peuvent être difficiles à régler et à contrôler. La taille de la file d'attente et la taille maximale du pool peuvent avoir besoin de se transformer mutuellement: l'utilisation de grandes files d'attente et de petits pools peut minimiser l'utilisation du processeur, les ressources du système d'exploitation et la commutation de contexte, mais peuvent entraîner une réduction manuelle du débit. Si les tâches sont fréquemment bloquées (par exemple, si ce sont des limites d'E / S), le système peut planifier du temps pour plus de threads que vous ne le permettez. L'utilisation de petites files d'attente nécessite généralement une taille de pool plus grande et a une utilisation plus élevée du processeur, mais peut rencontrer des frais généraux de planification inacceptable, ce qui peut également réduire le débit.
Parlons de la file d'attente du pool de threads ci-dessous, le diagramme de structure de classe est le suivant:
1) synchrone
La file d'attente correspond à la soumission directe mentionnée ci-dessus. Tout d'abord, la synchrone est illimitée, ce qui signifie que sa capacité à stocker les numéros est illimitée. Cependant, en raison des caractéristiques de la file d'attente elle-même, après avoir ajouté des éléments, vous devez attendre que d'autres fils les emportent avant de continuer à les ajouter.
2) LinkedBlockingQueue
La file d'attente correspond à la file d'attente illimitée ci-dessus.
3) ArrayBlockingQueue
La file d'attente correspond à la file d'attente limitée ci-dessus. ArrayBlockingQueue a les 3 constructeurs suivants:
public ArrayBlockingQueue (int la capacité) {this (capacité, false); } public ArrayBlockingQueue (Int Capacité, Boolean Fair) {if (Capacité <= 0) Jetez un nouveau IllégalArgumentException (); this.items = (e []) nouvel objet [capacité]; Lock = new ReentrantLock (Fair); NotEmpty = Lock.NewCondition (); notull = lock.newCondition (); } public ArrayBlockingQueue (Capacité int, foire booléenne, collection <? étend e> c) {this (capacité, foire); if (capacité <c.size ()) jetez un nouveau IllégalArgumentException (); for (iterator <? étend e> it = c.iterator (); it.hasnext ();) add (it.next ()); }Concentrons-nous sur cette foire. Fair représente la stratégie de concurrence des threads d'accès aux files d'attente. Lorsque cela est vrai, les files d'attente d'insertion des tâches sont conformes aux règles FIFO. Si faux, vous pouvez "couper la file d'attente". Par exemple, s'il existe de nombreuses tâches que la file d'attente maintenant, un fil a terminé la tâche et qu'une nouvelle tâche vient. S'il est faux, cette tâche n'a pas besoin d'être mis en file d'attente dans la file d'attente. Vous pouvez couper directement la file d'attente, puis l'exécuter. Comme indiqué dans la figure ci-dessous:
6. Stratégie d'exécution de rejet du pool de threads
Lorsque le nombre de threads atteint la valeur maximale, les tâches arrivent toujours en ce moment, et pour le moment, je dois refuser d'accepter les tâches.
ThreadPoolExecutor permet la personnalisation des stratégies d'exécution lors de l'ajout d'une tâche échoue. Vous pouvez appeler la méthode SetRejectEdExecutionHandler () du pool de threads et remplacer la politique existante par l'objet RejectEdExecutionHandler personnalisé. La stratégie de traitement par défaut fournie par ThreadPoolExecutor est de éliminer directement et de lancer des informations d'exception en même temps. ThreadPoolExecutor fournit 4 politiques existantes, à savoir:
ThreadPoolExecutor.AbortPolicy: indique que la tâche est rejetée et une exception est lancée. Le code source est le suivant:
Classe statique publique AbortPolicy Implémentez RejectEdExecutionHandler {/ ** * Crée un <TT> AbortPolicy </TT>. * / public abortpolicy () {} / ** * lève toujours RejectEdExecutionException. * @param r La tâche Runnable a demandé à être exécutée * @param e L'exécuteur tentant d'exécuter cette tâche * @throws rejectEdExecutionException toujours. * / public void rejectEdExecution (runnable r, threadpoolExecutor e) {lance new rejectEdExecutionException (); // exception de lancer}}ThreadPoolExecutor.DiscardPolicy: Cela signifie que la tâche est rejetée mais aucune actions n'est effectuée. Le code source est le suivant:
classe statique publique DiscardPolicy implémente RejectEdExecutionHandler {/ ** * Crée un <TT> DiscardPolicy </TT>. * / public DiscardPolicy () {} / ** * ne fait rien, ce qui a pour effet de rejeter la tâche r. * @param r La tâche Runnable demandée à exécuter * @param e L'exécuteur tentant d'exécuter cette tâche * / public void rejectEdExecution (Runnable R, ThreadPoolExecutor e) {} // rejeter directement, mais ne faites rien}ThreadPoolExecutor.CallerSpolicy: indique que la tâche est rejetée et que la tâche est directement exécutée dans le thread de l'appelant. Le code source est le suivant:
classe statique publique CalleRrunspolicy implémente RejectEdExecutionHandler {/ ** * Crée un <TT> CallErrunspolicy </TT>. * / public callErrunspolicy () {} / ** * Exécute la tâche r dans le thread de l'appelant, à moins que l'exécuteur exécuteur * ait été fermé, auquel cas la tâche est rejetée. * @param r La tâche Runnable demandée à exécuter * @param e L'exécuteur tentant d'exécuter cette tâche * / public void rejectEdExecution (runnable r, threadpoolExecutor e) {if (! e.isshutdown ()) {r.run (); // Exécuter directement des tâches}}}ThreadPoolExecutor.DiscardolDestPolicy: Cela signifie que la première tâche dans la file d'attente de tâche est d'abord rejetée, puis la tâche est ajoutée à la file d'attente. Le code source est le suivant:
Classe statique publique DiscardoldStPolicy implémente RejectEdExecutionHandler {/ ** * Crée un <TT> DiscardoldestPolicy </tt> pour l'exécuteur donné. * / public DiscardolDestPolicy () {} public void rejectEdExecution (runnable r, threadpoolExecutor e) {if (! e.isshutdown ()) {e.getQueue (). Poll (); // rejette la première tâche dans la file d'attente e.execure (R); // Exécuter une nouvelle tâche}}}Lorsque les tâches arrivent en continu, une tâche sera interrogée à partir de la file d'attente, puis une nouvelle tâche sera exécutée.
Résumer
Ce qui précède est une explication détaillée du propre pool de threads du JDK introduit par l'éditeur. J'espère que ce sera utile à tout le monde. Si vous avez des questions, veuillez me laisser un message et l'éditeur répondra à tout le monde à temps. Merci beaucoup pour votre soutien au site Web Wulin.com!