Principe d'implémentation du pool de threads Java et analyse du code source

Principe d'implémentation du pool de threads Java et analyse du code source


avant-propos

Cet article a commencé à être écrit fin novembre 2019, et il a été retardé jusqu'à la fin de 2020, et il n'a été terminé qu'au début de 2021.

Le temps est trop rapide et trop lent ~ !

Je me souviens vaguement qu'en octobre 2019, lorsqu'un certain Dong a quitté la start-up et envisageait de passer un entretien pour un emploi, il m'a demandé le fil de discussion, le sauriez-vous ? Puis il m'a envoyé une note que j'ai écrite en 2017 "Java Concurrent Programming Thread Pool Essential Knowledge Points", il a dit c'est ça? À cette époque, je pensais qu'il y avait presque autant de pools de threads~ !

Le 9 novembre 2019, un certain Dong et moi avons pris le bus 815 de Dawang Road à Yanjiao. À ce moment-là, c'était juste parce que j'apprenais quelques points de connaissances liés au multithreading, et nous avons juste bavardé dans le bus parce qu'il n'y avait rien à faire. À ce moment-là, il m'a posé quelques questions sur le pool de threads.Je pense que dans le pool de threads de travail habituel, si vous savez vous en servir, vous pouvez optimiser au maximum le nombre de threads principaux. La discussion principale est liée à la concurrence multithread et aux verrous.

À la fin de l'année, j'étais généralement occupé au travail, donc je faisais rarement de l'auto-apprentissage. Après une semaine, je me suis souvenu de la question posée par Dong Dong, "Comment sont générés les threads dans le pool de threads, et comment les tâches attendre l'exécution ?".

Je ne suis pas très clair sur la logique de cette pièce, j'ai donc temporairement enregistré cet élément TODO, et je pense que je l'étudierai quand j'aurai le temps. En conséquence, cela s'est étendu sur 2019 et 2020 et est arrivé directement à 2021.

Pardonnez ma tergiversation, le point clé est que la durée de cet article est trop longue et qu'il m'a laissé une profonde impression. Je dois en parler, commençons à passer aux choses sérieuses ~ !

JDK1.8Le code source pour analyser la conception de base et l'implémentation du pool de threads Java.

Cet article fait référence au principe d'implémentation du pool de threads Java et à sa pratique dans l'entreprise de Meituan .

Le principe d'implémentation du pool de threads Java et sa pratique dans l'entreprise Meituan Cet article est très bien écrit. En plus du contenu de cet article, cet article décrit également l'arrière-plan du pool de threads , la pratique et la dynamique du pool de threads en entreprise Par conséquent, si vous souhaitez en savoir plus sur ces types de pools de threads, vous pouvez lire l' article sur le principe d'implémentation des pools de threads Java et sa pratique dans le métier de Meituan .

Si le lecteur est un étudiant qui fait du développement côté serveur, il est fortement recommandé de lire le principe d'implémentation du pool de threads Java et sa mise en pratique dans le métier Meituan .

Extérieur

L'apparence est principalement constituée de certains points que nous voyons habituellement lors de l'utilisation du pool de threads.

  • Relation d'héritage ;
  • Constructeur;
  • paramètres dans le constructeur ;
  • Blocage de la file d'attente dans le constructeur ;
  • Création d'un pool de threads ;
  • Politique de refus dans le constructeur ;

héritage du pool de threads

ThreadPoolExecutor-uml.png

ThreadPoolExecutorL'interface de niveau supérieur implémentée est que Executordans l'interface, Executorles utilisateurs n'ont pas besoin de prêter attention à la façon de créer des threads et de planifier des threads pour exécuter des tâches. Les utilisateurs n'ont qu'à fournir des objets Runnable, soumettre la logique d'opération de tâche à l'exécuteur Executoret Executorle framework complète le déploiement des threads et la partie exécution des tâches.

ExecutorServiceL'interface ajoute quelques fonctionnalités :

  1. Étendre la possibilité d'exécuter des tâches, en complétant Futureles méthodes pouvant être générées pour une ou un lot de tâches asynchrones ;
  2. Fournit des méthodes pour gérer et contrôler le pool de threads, telles que l'arrêt du fonctionnement du pool de threads.

AbstractExecutorServiceC'est la classe abstraite de la couche supérieure, qui connecte le processus d'exécution des tâches en série, garantissant que l'implémentation de la couche inférieure ne doit se concentrer que sur une seule méthode d'exécution des tâches.

La classe d'implémentation de niveau le plus bas ThreadPoolExecutorimplémente la partie opération la plus complexe :

  1. Un ensemble d'un nombre spécifié de threads peut être automatiquement créé, géré et réutilisé, et la partie concernée n'a qu'à soumettre la tâche

  2. Sécurité des threads, ThreadPoolExecutorétat interne, nombre de threads centraux, threads non centraux et autres attributs, utilisation intensive des mécanismes de verrouillage CAS et AQS pour éviter les conflits causés par la concurrence

  3. Fournit les concepts de threads principaux, de files d'attente de blocage de tampon, de threads non essentiels et de stratégies de suppression, qui peuvent être combinés et utilisés en fonction des scénarios d'application réels

  4. Fournit beforeExecuteet afterExecute()peut prendre en charge l'extension de la fonction du pool de threads

Constructeur

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    
    
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  • corePoolSize : nombre de threads principaux dans le pool de threads. Généralement, qu'il y ait ou non des tâches, elles survivront toujours dans le pool de threads. Ce n'est que lorsque ThreadPoolExecutorla .allowCoreThreadTimeOut(boolean value) S'il n'y a pas de nouvelles tâches dans le délai spécifié trueQuand cela arrive, le thread principal sera également terminé, et cet intervalle de temps keepAliveTimeest spécifié .
  • maximumPoolSize : nombre maximal de threads que le pool de threads peut prendre en charge. Lorsque le nombre de threads actifs atteint cette valeur, les nouvelles tâches suivantes seront bloquées.
  • keepAliveTime : contrôle le délai d'expiration lorsque le thread est inactif. S'il est dépassé, le thread sera terminé. Généralement utilisé pour les threads non centraux, ThreadPoolExecutoruniquement lorsqueallowCoreThreadTimeOut(boolean value) la méthode dans est définie sur , il agit également sur les threads principaux.true
  • unit : utilisé pour spécifier keepAliveTimel'unité de temps du paramètre, TimeUnitc'est un enumtype d'énumération, couramment utilisés sont : TimeUnit.HOURS(小时), TimeUnit.MINUTES(分钟), TimeUnit.SECONDS(秒) et TimeUnit.MILLISECONDS(毫秒)etc.
  • workQueue : La file d'attente des tâches du pool de threads, execute(Runnable command)la tâche sera Runnablestockée dans la file d'attente via la méthode du pool de threads.
  • threadFactory : usine de threads, qui est une interface utilisée pour créer de nouveaux threads pour le pool de threads.
  • gestionnaire : stratégie de rejet. La stratégie dite de rejet fait référence à la stratégie correspondante adoptée par le pool de threads pour rejeter la tâche lorsque la tâche est ajoutée au pool de threads.

Variables membres

/**
 * 任务阻塞队列 
 */
private final BlockingQueue<Runnable> workQueue; 
/**
 * 非公平的互斥锁(可重入锁)
 */
private final ReentrantLock mainLock = new ReentrantLock();
/**
 * 线程集合一个Worker对应一个线程,没有核心线程的说话,只有核心线程数
 */
private final HashSet<Worker> workers = new HashSet<Worker>();
/**
 * 配合mainLock通过Condition能够更加精细的控制多线程的休眠与唤醒
 */
private final Condition termination = mainLock.newCondition();
/**
 * 线程池中线程数量曾经达到过的最大值。
 */
private int largestPoolSize;  
/**
 * 已完成任务数量
 */
private long completedTaskCount;
/**
 * ThreadFactory对象,用于创建线程。
 */
private volatile ThreadFactory threadFactory;  
/**
 * 拒绝策略的处理句柄
 * 现在默认提供了CallerRunsPolicy、AbortPolicy、DiscardOldestPolicy、DiscardPolicy
 */
private volatile RejectedExecutionHandler handler;
/**
 * 线程池维护线程(超过核心线程数)所允许的空闲时间
 */
private volatile long keepAliveTime;
/**
 * 允许线程池中的核心线程超时进行销毁
 */
private volatile boolean allowCoreThreadTimeOut;  
/**
 * 线程池维护线程的最小数量,哪怕是空闲的  
 */
private volatile int corePoolSize;
/**
 * 线程池维护的最大线程数量,线程数超过这个数量之后新提交的任务就需要进入阻塞队列
 */
private volatile int maximumPoolSize;

Créer un pool de threads

ExecutorsFournit des méthodes pour obtenir plusieurs pools de threads couramment utilisés :

  • Pool de threads de cache

newCachedThreadPoolEst un pool de threads qui crée de nouveaux threads selon les besoins, mais réutilise les threads précédemment construits au fur et à mesure 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' execute()appel réutilisera un thread précédemment construit (si un thread est disponible). Si aucun thread existant n'est disponible, un nouveau thread est créé et ajouté au pool. Termine et supprime les threads qui n'ont pas été utilisés pendant 60 secondes du cache. Par conséquent, un pool de threads qui reste inactif pendant une longue période n'utilisera aucune ressource. Notez que des pools de threads avec des propriétés similaires mais des détails différents (tels que des paramètres de délai d'attente) peuvent être créés à l'aide du ThreadPoolExecutorconstructeur .

public static ExecutorService newCachedThreadPool() {
    
    
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                  60L, TimeUnit.SECONDS,
                  new SynchronousQueue<Runnable>());
}
  • pool de threads à thread unique

newSingleThreadExecutorCréez un pool à thread unique, c'est-à-dire que le pool de threads n'a qu'un seul thread qui fonctionne et que toutes les tâches sont exécutées en série. Si le seul thread se termine de manière anormale, un nouveau thread le remplacera. Ce pool de threads Assurez-vous que l'ordre d'exécution de tous tâches est exécutée dans l'ordre dans lequel les tâches sont soumises.

public static ExecutorService newSingleThreadExecutor() {
    
    
  return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>()));
}
  • pool de threads de taille fixe

newFixedThreadPoolCréez un pool de threads de taille fixe et 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. Une fois que le pool de threads atteint la taille maximale, il reste inchangé. Si un thread se termine en raison de une exécution anormale, alors le pool de threads ajoutera un nouveau thread.

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    
    
  return new ThreadPoolExecutor(nThreads, nThreads,
                  0L, TimeUnit.MILLISECONDS,
                  new LinkedBlockingQueue<Runnable>(),
                  threadFactory);
}
  • pool de threads à thread unique

newScheduledThreadPoolCréez un pool de threads de taille illimitée qui prend en charge la synchronisation et l'exécution périodique des tâches.

public static ScheduledExecutorService newScheduledThreadPool(
    int corePoolSize, ThreadFactory threadFactory) {
  return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                   ThreadFactory threadFactory) {
  super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
      new DelayedWorkQueue(), threadFactory);
}

Nous pouvons voir que la méthode ci-dessus utilise un total de DelayedWorkQueue, LinkedBlockingQueueet SynchronousQueue. Il s'agit de la file d'attente de blocage, l'un des cœurs de thread.

file d'attente de blocage des tâches

BlockingQueue.png

Il est généralement divisé en files d'attente de soumission directe, files d'attente de tâches limitées, files d'attente de tâches illimitées et files d'attente de tâches prioritaires ;

File d'attente synchrone

1. File d'attente de soumission directe : définie comme SynchronousQueueune file d'attente, SynchronousQueueelle est spéciale BlockingQueue, elle n'a pas de capacité, elle sera bloquée à chaque fois qu'une opération d'insertion est effectuée, et elle ne sera réveillée qu'après une autre opération de suppression, sinon à chaque suppression l'opération doit attendre l'opération d'insertion correspondante.

En utilisant SynchronousQueuela file d'attente, les tâches soumises ne seront pas enregistrées et seront toujours soumises pour exécution immédiatement. Si le nombre de threads utilisés pour exécuter la tâche est inférieur à maximumPoolSize, essayez de créer un nouveau processus, s'il atteint maximumPoolSizela valeur maximale définie, il handlerrejettera l'exécution en fonction de la politique que vous avez définie. Par conséquent, les tâches que vous soumettez de cette manière ne seront pas mises en cache, mais seront exécutées immédiatement. Dans ce cas, vous devez avoir une évaluation précise de la simultanéité de votre programme avant de pouvoir définir le nombre approprié, sinon c'est facile maximumPoolSize. la politique de rejet sera mise en œuvre ;

ArrayBlockingQueueArrayBlockingQueue

2. File d'attente de tâches délimitée : Une file d'attente de tâches délimitée peut être implémentée ArrayBlockingQueuecomme suit :

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

En utilisant ArrayBlockingQueueune file d'attente de tâches limitée, si une nouvelle tâche doit être exécutée, le pool de threads créera un nouveau thread, jusqu'à ce que le nombre de threads créés atteigne corePoolSize, la nouvelle tâche sera ajoutée à la file d'attente. Si la file d'attente est pleine, c'est-à-dire qu'elle dépasse ArrayBlockingQueuela capacité initialisée, continuez à créer des threads jusqu'à ce que le nombre de threads atteigne le maximumPoolSizenombre maximal de threads défini, et s'il est supérieur maximumPoolSize, exécutez la stratégie de rejet. Dans ce cas, la limite supérieure du nombre de threads est directement liée à l'état de la file d'attente de tâches bornée. Si la capacité initiale de la file d'attente bornée est importante ou si l'état de surcharge n'est pas atteint, le nombre de threads sera toujours maintenu en dessous de E. Sinon, lorsque la corePoolSizfile d'attente des tâches Lorsqu'elle est pleine, le nombre maximal de threads maximumPoolSizesera plafonné.

LinkedBlockingQueueLinkedBlockingQueue

3. File d'attente de tâches illimitée : La file d'attente de tâches illimitée peut être implémentée LinkedBlockingQueuecomme suit :

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

En utilisant une file d'attente de tâches illimitée, la file d'attente de tâches du pool de threads peut ajouter de nouvelles tâches sans limite, et le nombre maximum de threads créés par le pool de threads est le nombre que vous avez défini, c'est-à-dire que ce paramètre n'est pas valide dans ce corePoolSizecas maximumPoolSize, même s'il y a de nombreuses tâches non exécutées en cache dans la file d'attente des tâches. Lorsque le nombre de threads dans le pool de threads atteint corePoolSize, il n'augmentera plus ; si de nouvelles tâches sont ajoutées ultérieurement, elles entreront directement dans la file d'attente et attendront. Lors de l'utilisation ce mode de file d'attente de tâches, assurez-vous de faire attention à la coordination et au contrôle entre la soumission et le traitement de votre tâche, sinon il y aura un problème que les tâches dans la file d'attente continueront de croître en raison de l'incapacité de les traiter à temps jusqu'à ce que les ressources sont épuisés.

PriorityBlockingQueue

4. File d'attente des tâches prioritaires : La file d'attente des tâches prioritaires est PriorityBlockingQueuemise en œuvre par :

Les tâches seront réorganisées et exécutées en fonction de la priorité, et le nombre de threads dans le pool de threads est toujours corePoolSize, c'est-à-dire qu'il n'y en a qu'un.

PriorityBlockingQueueEn fait, il s'agit d'une file d'attente illimitée spéciale. Peu importe le nombre de tâches qui y sont ajoutées, le nombre de threads créés par le pool de threads ne dépassera pas le nombre maximum, mais les autres files d'attente corePoolSizetraitent généralement les tâches selon le premier en premier. -out règle, et PriorityBlockingQueuela file d'attente peut être personnalisée Les règles sont exécutées séquentiellement selon l'ordre de priorité des tâches.

En fait, LinkedBlockingQueuela limite peut également être définie et sa limite par défaut est Integer.MAX_VALUE. En même temps, il prend également en charge la définition de la taille de la file d'attente lors de la construction.

politique de rejet

public interface RejectedExecutionHandler {
    
    
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

Lorsqu'il Executora été fermé, c'est-à-dire après l'exécution de la méthode executorService.shutdown(), ou Executorlorsque des limites délimitées sont utilisées pour la capacité maximale du thread et de la file d'attente de travail, et ont été saturées. execute()Les nouvelles tâches soumises à l'aide de la méthode seront rejetées.
Dans le cas ci-dessus, executela méthode appellera sa méthodeRejectedExecutionHandler .RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)

Politique de rejet par défaut AbortPolicy

Aussi connu sous le nom de stratégie de résiliation, un rejet lève le runtime RejectedExecutionException. Le côté métier peut obtenir des commentaires en temps opportun sur les résultats soumis pour cette tâche en interceptant les exceptions.

public static class AbortPolicy implements RejectedExecutionHandler {
    
    
  public AbortPolicy() {
    
     }
  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
  }
}

CallerRunsPolicyCallerRunsPolicy

Le fait d'avoir un contrôle de rétroaction autonome qui permet aux soumissionnaires d'effectuer des tâches de soumission peut ralentir la soumission de nouvelles tâches. Dans ce cas, il faut laisser toutes les tâches s'exécuter.

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    
    
    public CallerRunsPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        if (!e.isShutdown()) {
    
    
            r.run();
        }
    }
}

DiscardPolicy

Un gestionnaire pour rejeter des tâches, supprimer silencieusement des tâches. En utilisant cette stratégie, nous ne pourrons peut-être pas percevoir l'état anormal du système. À utiliser avec prudence~ !

public static class DiscardPolicy implements RejectedExecutionHandler {
    
    
    public DiscardPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
    }
}

DiscardOldestPolicy

Ignorez la tâche la plus avancée dans la file d'attente et soumettez à nouveau les tâches rejetées. L'utilisation de cette stratégie dépend du fait que l'entreprise doit être remplacée par la nouvelle et l'ancienne, alors utilisez-la avec prudence~ !

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    
    
    public DiscardOldestPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        if (!e.isShutdown()) {
    
    
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

noyau

J'ai parlé de l'apparence du pool de threads plus tôt , puis je parlerai de son cœur .

Le pool de threads construit en fait un modèle producteur-consommateur en interne , qui sera découplé 线程des 任务deux et non directement lié, de manière à bien tamponner les tâches et réutiliser les threads.

Le fonctionnement du pool de threads est principalement divisé en deux parties : la gestion des tâches et la gestion des threads.

La partie gestion des tâches agit comme un producteur. Lorsqu'une tâche est soumise, le pool de threads jugera du flux suivant de la tâche :

  1. Appliquer directement le fil pour effectuer la tâche ;
  2. Buffer dans la file d'attente et attendre que le thread s'exécute ;
  3. Refuser la tâche.

La partie gestion des threads est le consommateur, qui est maintenu uniformément dans le pool de threads, et le thread est alloué en fonction de la demande de tâche. Une fois que le thread a exécuté la tâche, il continue d'obtenir de nouvelles tâches à exécuter. Enfin, lorsque le thread ne peut pas obtenir la tâche, le fil sera recyclé.

Ensuite, nous expliquerons en détail le mécanisme de fonctionnement du pool de threads selon les trois parties suivantes :

  1. Comment le pool de threads maintient son propre état.
  2. Comment le pool de threads gère les tâches.
  3. Comment le pool de threads gère les threads.

Le cycle de vie du pool de threads

L'état d'exécution du pool de threads n'est pas explicitement défini par l'utilisateur, mais est maintenu en interne avec l'exécution du pool de threads.

Le pool de threads utilise en interne une variable pour conserver deux valeurs : l'état d'exécution ( runState) et le nombre de threads ( workerCount).

Dans l'implémentation spécifique, le pool de threads regroupe la maintenance de deux paramètres clés, l'état d'exécution ( runState) et le nombre de threads ( ) :workerCount

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctlCe AtomicIntegertype est un champ qui contrôle l'état d'exécution du pool de threads et le nombre de threads valides dans le pool de threads.

Il contient deux parties d'informations en même temps : l'état d'exécution du pool de threads ( runState) et le nombre de threads effectifs dans le pool de threads ( workerCount), les 3 bits supérieurs sont enregistrés runStateet les 29 bits inférieurs sont enregistrés workerCount, et le deux variables n'interfèrent pas l'une avec l'autre.

L'utilisation d'une variable pour stocker deux valeurs peut éviter les incohérences lors de la prise de décisions pertinentes, et il n'est pas nécessaire d'occuper des ressources de verrouillage afin de maintenir la cohérence entre les deux. Il peut également être trouvé en lisant le code source du pool de threads qu'il est souvent nécessaire de juger de l'état de fonctionnement du pool de threads et du nombre de threads en même temps. Le pool de threads fournit également plusieurs méthodes permettant à l'utilisateur d'obtenir l'état d'exécution actuel et le nombre de threads du pool de threads. La méthode d'opération de bit est utilisée ici, qui est beaucoup plus rapide que l'opération de base (PS : cette utilisation peut être vue dans de nombreux codes sources).

La méthode de calcul permettant d'obtenir l'état du cycle de vie et d'obtenir le nombre de threads du pool de threads dans le package interne est illustrée dans le code suivant :

private static final int COUNT_BITS = Integer.SIZE - 3;//32-3
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;//低29位都为1,高位都为0

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;//111
private static final int SHUTDOWN   =  0 << COUNT_BITS;//000
private static final int STOP       =  1 << COUNT_BITS;//001
private static final int TIDYING    =  2 << COUNT_BITS;//010
private static final int TERMINATED =  3 << COUNT_BITS;//011

// Packing and unpacking ctl
//计算当前运行状态,取高三位
private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }
//计算当前线程数量,取低29位
private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }
//通过状态和线程数生成ctl
private static int ctlOf(int rs, int wc) {
    
     return rs | wc; }

ThreadPoolExecutorIl existe 5 états de fonctionnement, à savoir :

État de fonctionnement description de l'état
EN COURS Peut accepter des tâches nouvellement soumises et peut également traiter des tâches dans la file d'attente de blocage
FERMER Impossible d'accepter les tâches nouvellement soumises, mais peut continuer à traiter les tâches dans la file d'attente de blocage
ARRÊT Il ne peut pas accepter de nouvelles tâches, ni traiter des tâches dans la file d'attente tout en interrompant le thread de tâche de traitement
RANGEMENT Toutes les tâches ont été terminées, workCount (nombre de threads effectifs) est 0
TERMINÉ Entrer dans cet état après l'exécution de la méthode terminée

Instruction de pool de threads cycle.jpg

Mécanisme de planification des tâches

La planification des tâches est le point d'entrée principal du pool de threads. Lorsqu'un utilisateur soumet une tâche, la manière dont la tâche sera exécutée ensuite est déterminée par cette étape. Comprendre cette partie équivaut à comprendre le mécanisme de fonctionnement de base du pool de threads.

Tout d'abord, la planification de toutes les tâches est effectuée par la méthode. Le travail effectué dans cette partie consiste à : vérifier l' état d'exécutionexecute actuel du pool de threads , le nombre de threads en cours d'exécution et la stratégie d'exécution , et déterminer le prochain processus d'exécution , s'il faut appliquer directement l' exécution du thread , ou mis en mémoire tampon dans la file d' exécution , ou rejeter directement la tâche . Son processus d'exécution est le suivant :

  1. Vérifiez d'abord l'état d'exécution du pool de threads, sinon RUNNING, rejetez-le directement, et le pool de threads doit s'assurer que RUNNINGla tâche est exécutée dans un certain état.
  2. Si workerCount < corePoolSize, un thread est créé et lancé pour exécuter la tâche nouvellement soumise.
  3. Si workerCount >= corePoolSize, et que la file d'attente de blocage dans le pool de threads n'est pas pleine, ajoutez la tâche à la file d'attente de blocage.
  4. Si workerCount >= corePoolSize && workerCount < maximumPoolSize, et que la file d'attente de blocage dans le pool de threads est pleine, créez et démarrez un thread pour exécuter la tâche nouvellement soumise.
  5. Si workerCount >= maximumPoolSize, et que la file d'attente de blocage dans le pool de threads est pleine, la tâche sera traitée conformément à la stratégie de rejet, et la méthode de traitement par défaut consiste à lever une exception directement.

Organigramme de planification des tâches.png

Ensuite, entrez le temps d'analyse du code source~ !

soumettre la tâche

//AbstractExecutorService.java
public <T> Future<T> submit(Callable<T> task) {
    
    
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
//ThreadPoolExecutor.java
public void execute(Runnable command) {
    
    
  if (command == null)
    throw new NullPointerException();
  int c = ctl.get();//获取ctl
  //检查当前核心线程数,是否小于核心线程数的大小限制
  if (workerCountOf(c) < corePoolSize) {
    
    
    //没有达到核心线程数的大小限制,那么添家核心线程执行该任务
    if (addWorker(command, true))
      return;
    //如果添加失败,刷新ctl值
    c = ctl.get();
  }
  //再次检查线程池的运行状态,将任务添加到等待队列中
  if (isRunning(c) && workQueue.offer(command)) {
    
    
    int recheck = ctl.get();//刷新ctl值
    //如果当前线程池的装不是运行状态,那么移除刚才添加的任务
    if (! isRunning(recheck) && remove(command))
      reject(command);//移除成功后,使用拒绝策略处理该任务;
    else if (workerCountOf(recheck) == 0)//当前工作线程数为0
      //线程池正在运行,或者移除任务失败。
      //添加一个非核心线程,并不指定该线程的运行任务。
      //等线程创建完成之后,会从等待队列中获取任务执行。
      addWorker(null, false);
  } 
  //逻辑到这里说明线程池已经不是RUNNING状态,或者等待队列已满,需要创建一个新的非核心线程执行该任务;
  //如果创建失败,那么非核心线程已满,使用拒绝策略处理该任务;
  else if (!addWorker(command, false))
    reject(command);
}

Ajouter des threads de travail et exécuter des tâches

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
  Worker(Runnable firstTask) {
    
    
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;//初始化的任务,可以为null
    this.thread = getThreadFactory().newThread(this);//Worker持有的线程
  }
  /**部分代码省略*/
	public void run() {
    
    
  	runWorker(this);
	}
}

Ajouter des travailleurs et exécuter des tâches : le tout est de créer Workeret de trouver une correspondance Runnable.

addwork.png

Ajouter un fil de travail

L'ajout de threads s'effectue via la méthode dans le pool de threads addWorker. La fonction de cette méthode est d'ajouter un thread. Cette méthode ne tient pas compte de l'étape à laquelle le pool de threads ajoute le thread. Cette stratégie d'allocation de threads est terminée à l'étape précédente. Cette l'étape est seulement Terminer l'augmentation du thread, le faire fonctionner et enfin renvoyer le résultat indiquant s'il a réussi ou non.

addWorkerLa méthode a deux paramètres : firstTask, core.

firstTaskLe paramètre est utilisé pour spécifier la première tâche exécutée par le thread nouvellement ajouté, qui peut être vide ;

coreLe paramètre truesignifie que lors de l'ajout d'un nouveau thread, il jugera si le nombre de threads actifs actuels est inférieur à corePoolSize, falseet cela signifie qu'il doit juger si le nombre de threads actifs actuels est inférieur à avant d'ajouter un nouveau thread maximumPoolSize.

addwork2.png

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
    retry://breakcontinue的跳出标签
    for (;;) {
    
    
        int c = ctl.get();//获取ctl的值
        int rs = runStateOf(c);//获取当前线程池的状态;
        /**
         * 1、如果当前的线程池状态不是RUNNING
         * 2、当前线程池是RUNNING而且没有添加新任务,而且等待队列不为空。这种情况下是需要创建执行线程的。
         * 所以满足1,但不满足2就创建执行线程失败,返回false。
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
                firstTask == null &&
                ! workQueue.isEmpty()))
            return false;
        /**进入内层循环 */
        for (;;) {
    
    
            int wc = workerCountOf(c);//获取当前执行线程的数量
            /**
             * 1、工作线程数量大于或等于计数器的最大阈值,那么创建执行线程失败,返回false。
             * 2、如果当前创建的核心线程,那么工作线程数大于corePoolSize的话,创建执行线程失败,返回false。
             * 3、如果当前创建的是非核心线程,那么工作线程数大于maximumPoolSize的话,创建执行线程失败,返回false。
             */
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //用CAS操作让线程数加1,如果成功跳出整个循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)//线程状态前后不一样,重新执行外循环
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
            //如果CAS操作由于工作线程数的增加失败,那么重新进行内循环
        }
    }
    /**就现在,线程数已经增加了。但是真正的线程对象还没有创建出来。*/
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    
    
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
    
    
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();//加锁
            try {
    
    
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());
                /**
                 * 再次检查线程池的运行状态
                 * 1、如果是RUNNING状态,那么可以创建;
                 * 2、如果是SHUTDOWN状态,但没有执行线程,可以创建(创建后执行等待队列中的任务)
                 */
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
    
    
                    //检测该线程是否已经开启
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);//将改工作线程添加到集合中
                    int s = workers.size();
                    if (s > largestPoolSize)//更新线程池的运行时的最大线程数
                        largestPoolSize = s;
                    workerAdded = true;//标识工作线程添加成功
                }
            } finally {
    
    //释放锁
                mainLock.unlock();
            }
            if (workerAdded) {
    
    //如果工作线程添加成功,那么开启线程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
    
    
        //如果工作线程添加失败,那么进行失败处理
        //将已经增加的线程数减少,将添加到集合的工作线程删除
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

effectuer des tâches

Nous avons vu dans la section sur l'ajout d'un thread de travail qu'une fois l'ajout réussi, le thread sera lancé pour exécuter la tâche.

runwork.png

final void runWorker(Worker w) {
    
    
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    //解锁,允许中断
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
    
    
        //如果当前的工作线程已经有执行任务,或者可以从等待队列中获取到执行任务
        //getTask获取任务时候会进行阻塞
        while (task != null || (task = getTask()) != null) {
    
    
            w.lock();//开始执行,上锁
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            //判断线程是否需要中断
            //如果线程池状态是否为STOP\TIDYING\TERMINATED,同时当前线程没有被中断那么将当前线程进行中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                    runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
    
    
                beforeExecute(wt, task);
                Throwable thrown = null;//异常处理
                try {
    
    
                    task.run();
                } catch (RuntimeException x) {
    
    
                    thrown = x; throw x;
                } catch (Error x) {
    
    
                    thrown = x; throw x;
                } catch (Throwable x) {
    
    
                    thrown = x; throw new Error(x);
                } finally {
    
    
                    afterExecute(task, thrown);
                }
            } finally {
    
    
                task = null;//工作线程的当前任务置空
                w.completedTasks++;//当前工作线程执行完成的线程数+1
                w.unlock();//执行完成解锁
            }
        }
        completedAbruptly = false;//完成了所有任务,正常退出
    } finally {
    
    //执行工作线程的退出操作
        processWorkerExit(w, completedAbruptly);
    }
}

Le thread de travail obtient la tâche

getTask.jpg

private Runnable getTask() {
    
    
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
    
    
        int c = ctl.get();//获取ctl的值
        int rs = runStateOf(c);//获取线程池状态

        // Check if queue empty only if necessary.
        /**
         * 1、rs为STOP\TIDYING\TERMINATED,标识无法继续执行任务
         * 2、等待队列中没有任务可以被执行
         * 工作线程数量减一
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);//获取工作线程数量
        // Are workers subject to culling?
        //如果允许核心线程超时,或者当前工作线程数量大于核心线程数量。标识需要进行超时检测
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        /**
         * 1、如果当前工作线程数是否大于线程池可允许的最大工作线程数(maximumPoolSize可以动态设置)
         * ,或者当前需要进行超时控制并且上次从等待队列中获取执行任务发生了超时。
         * 2、如果当前不是唯一的线程,并且等待队列中没有需要执行的任务。
         * 这两种情况下一起存在就表示,工作线程发生了超时需要回收,所以对线程数进行-1;
         */
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
    
    
            if (compareAndDecrementWorkerCount(c))//线程数量减少成功,否则重新执行本次循环
                return null;
            continue;
        }
        try {
    
    
            //如果设置有超时,那么设定超时时间。否则进行无限的阻塞等待执行任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;//获取超时,设置标记
        } catch (InterruptedException retry) {
    
    
            timedOut = false;
        }
    }
}

sortie du thread de travail

La destruction des threads dans le pool de threads dépend du recyclage automatique de la JVM . Le rôle du pool de threads est de maintenir un certain nombre de références de threads en fonction de l'état actuel du pool de threads pour éviter que ces threads ne soient recyclés par la machine virtuelle. JVM. Lorsque le pool de threads décide quels threads doivent être recyclés, vous devez uniquement supprimer sa référence. WorkerAprès avoir été créé, l'interrogation sera effectuée en continu, puis la tâche sera acquise pour exécution. Le thread principal peut attendre indéfiniment pour acquérir la tâche, et le thread non principal doit acquérir la tâche dans un délai limité. Lorsque Workerla tâche ne peut pas être obtenue, c'est-à-dire que la tâche obtenue est vide, la boucle se termine et Workerelle élimine activement sa propre référence dans le pool de threads.

processWorkerExit.png

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
    //completedAbruptly为true,标识该工作线程执行出现了异常,将工作线程数减一
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();
    //否则标识该工作线程为正常结束,这种情况下getTask方法中已经对工作线程进行了减一
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();//加锁
    try {
    
    
        completedTaskCount += w.completedTasks;//更新线程池的,线程执行完成数量
        workers.remove(w);//工作线程容器移除该工作线程
    } finally {
    
    
        mainLock.unlock();//解锁
    }
    //尝试结束线程池
    tryTerminate();
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
    
    //如果当前线程池的运行状态是RUNNING\SHUTDOWN
        if (!completedAbruptly) {
    
    //如果该工作线程为正常结束
            /**
             * 判断当前需要的最少的核心线程数(如果允许核心线程超时,那么最小的核心线程数为0,否则为corePoolSize)
             */
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            //如果允许核心线程超时,而且等待队列不为空,那么工作线程的最小值为1,否则为0。
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            //当前工作线程数,是否满足最先的核心线程数
            if (workerCountOf(c) >= min)
                //如果满足那么直接return
                return; // replacement not needed
        }
        //如果是异常结束,或者当前线程数不满足最小的核心线程数,那么添加一个非核心线程
        //核心线程和非核心线程没有什么不同,只是在创建的时候判断逻辑不同
        addWorker(null, false);
    }
}

besoins spéciaux

Surveillance du pool de threads

Surveillez les paramètres fournis par le pool de threads. Certains attributs du pool de threads peuvent être utilisés lors de la surveillance du pool de threads

  • getTaskCount: Le nombre total de tâches exécutées et non exécutées dans le pool de threads ;
  • getCompletedTaskCount: Le nombre de tâches terminées par le pool de threads, la valeur est inférieure ou égale à taskCount;
  • getLargestPoolSize: Le nombre maximum de threads jamais créés par le pool de threads. Grâce à ces données, nous pouvons savoir si le pool de threads est plein, c'est-à-dire s'il a été atteint maximumPoolSize;
  • getPoolSize: Le nombre actuel de threads dans le pool de threads ;
  • getActiveCount: nombre de threads exécutant actuellement des tâches dans le pool de threads.

Ajuster dynamiquement la taille du pool de threads

JDKPermet à l'utilisateur du pool de threads ThreadPoolExecutorde définir dynamiquement la stratégie principale du pool de threads via l'instance, setCorePoolSizecomme exemple de la méthode ;

Une fois que l'utilisateur du pool de threads d'exécution a appelé ce paramètre de méthode corePoolSize, le pool de threads écrase directement la corePoolSizevaleur d'origine et adopte différentes stratégies de traitement en fonction de la comparaison entre la valeur actuelle et la valeur d'origine.

Dans le cas où la valeur actuelle est inférieure au nombre actuel de threads de travail, cela signifie qu'il existe des workerthreads redondants. À ce moment, une demande d'interruption sera lancée vers le idlethread actuel workerpour réaliser le recyclage, et les redondants seront également recyclé la prochaine fois ; car la valeur actuelle est supérieure à la valeur d'origine et la workervaleur actuelle thread pour exécuter les tâches de la file d'attente (PS : l'état est l'état après le fil libère le verrou, car il est verrouillé pendant le fonctionnement).idelworkeridelworker

setCorePoolSize.png

public void setCorePoolSize(int corePoolSize) {
    
    
    if (corePoolSize < 0)
        throw new IllegalArgumentException();
    //计算增量
    int delta = corePoolSize - this.corePoolSize;
    //覆盖原有的corePoolSize
    this.corePoolSize = corePoolSize;
    //如果当前的工作线程数量大于线程池的最大可运行核心线程数量,那么进行中断工作线程处理
    if (workerCountOf(ctl.get()) > corePoolSize)
        interruptIdleWorkers();
    else if (delta > 0) {
    
    //如果增量大于0
        // We don't really know how many new threads are "needed".
        // As a heuristic, prestart enough new workers (up to new
        // core size) to handle the current number of tasks in
        // queue, but stop if queue becomes empty while doing so.
        //等待队列非空,获取等待任务和增量的最小值
        int k = Math.min(delta, workQueue.size());
        //循环创建核心工作线程执行等待队列中的任务
        while (k-- > 0 && addWorker(null, true)) {
    
    
            if (workQueue.isEmpty())
                break;
        }
    }
}
private void interruptIdleWorkers() {
    
    
    interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();//加锁
    try {
    
    
        //遍历工作线程的集合
        for (Worker w : workers) {
    
    
            Thread t = w.thread;
            //如果当前线程没有被中断,而且能获取到锁,那么尝试进行中断,最后释放锁
            if (!t.isInterrupted() && w.tryLock()) {
    
    
                try {
    
    
                    t.interrupt();
                } catch (SecurityException ignore) {
    
    
                } finally {
    
    
                    w.unlock();
                }
            }
            //是否仅仅中断一个工作线程
            if (onlyOne)
                break;
        }
    } finally {
    
    //释放锁
        mainLock.unlock();
    }
}

Fermer gracieusement le pool de threads

On peut également voir dans le diagramme "Thread Pool Statement Cycle" que lorsque nous exécutons ThreadPoolExecutor#shutdownla méthode l'état du pool de threads passera de RUNNING à SHUTDOWN . ThreadPoolExecutor#shutdownNowAprès l'appel, l'état du pool de threads passera de RUNNING à STOP .

fermer

Arrêtez d'accepter de nouvelles tâches et les tâches d'origine continuent de s'exécuter

  1. Arrêtez de recevoir de nouvelles tâches de soumission ;
  2. Les tâches soumises (y compris celles en cours d'exécution et celles en attente dans la file d'attente) continueront d'être exécutées ;
  3. Attendez que l'étape 2 soit terminée avant de vous arrêter réellement ;
public void shutdown() {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        checkShutdownAccess();// 检查权限
        advanceRunState(SHUTDOWN);// 设置线程池状态
        interruptIdleWorkers();// 中断空闲线程
        // 钩子函数,主要用于清理一些资源
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();
}

shutdownLa méthode verrouille d'abord, puis vérifie d'abord l'état d'installation du système. Ensuite, l'état du pool de threads sera changé en SHUTDOWN , après quoi le pool de threads n'acceptera plus de nouvelles tâches soumises. À ce stade, si vous continuez à soumettre des tâches au pool de threads, la stratégie de rejet du pool de threads sera utilisée pour répondre, qui sera utilisée par défaut et une exceptionThreadPoolExecutor.AbortPolicy sera levée .RejectedExecutionException

interruptIdleWorkersLa méthode est décrite dans le code source dans la section d' ajustement dynamique de la taille du pool de threads . Elle n'interrompra que les threads inactifs et n'interrompra pas les threads qui exécutent des tâches. Les threads inactifs seront bloqués dans la file d'attente de blocage du pool de threads.

shutdownNow

Arrêtez d'accepter de nouvelles tâches et les tâches d'origine cessent de s'exécuter

  1. Identique shutdown() à , arrêtez d'abord de recevoir de nouvelles submittâches ;
  2. Ignorer les tâches en attente dans la file d'attente ;
  3. Tentative d'interruption de la tâche en cours d'exécution interrupt;
  4. Renvoie une liste des tâches non exécutées ;
public List<Runnable> shutdownNow() {
    
    
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        checkShutdownAccess();// 检查状态
        advanceRunState(STOP);// 将线程池状态变为 STOP
        interruptWorkers();// 中断所有线程,包括工作线程以及空闲线程
        tasks = drainQueue();// 丢弃工作队列中存量任务
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
private void interruptWorkers() {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        for (Worker w : workers)
            //如果工作线程已经开始,那么调用interrupt进行中断
            w.interruptIfStarted();
    } finally {
    
    
        mainLock.unlock();
    }
}
private List<Runnable> drainQueue() {
    
    
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    //从此队列中删除所有可用的元素,并将它们添加到给定的集合中。
    q.drainTo(taskList);
    //如果队列是DelayQueue或其他类型的队列,而poll或drainTo可能无法删除某些元素,则会将它们逐个删除。
    if (!q.isEmpty()) {
    
    
        for (Runnable r : q.toArray(new Runnable[0])) {
    
    
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}

shutdownNowLa méthode consistant à essayer de terminer le thread est réalisée en appelant Thread.interrupt()la méthode , qui a un effet limité. S'il n'y a pas d'applications telles que sleep, wait, , time lock dans le thread, la méthode ne peut pas interrompre le thread en cours. Par conséquent, cela ne signifie pas que le pool de threads pourra se fermer immédiatement et qu'il devra peut-être attendre que toutes les tâches en cours d'exécution soient terminées avant de se fermer. Mais la plupart du temps, il est possible d'arrêter immédiatement.Conditioninterrupt()shutdownNow()

Mécanisme d'interruption de thread : thread#interruptle simple fait de définir un indicateur d'interruption n'interrompra pas immédiatement les threads normaux. Si vous souhaitez que l'interruption prenne effet immédiatement, vous devez appeler le thread pour Thread.interrupted()juger de l'état d'interruption du thread. Pour un thread bloqué, lorsqu'une interruption est appelée, le thread quitte immédiatement l'état bloqué et lève InterruptedExceptionune exception . Ainsi, pour les threads bloqués, InterruptedExceptionles exceptions .

attendreRésiliation

Le pool de threads shutdownet shutdownNowla méthode n'attendront pas activement la fin de la tâche d'exécution. Si vous devez attendre la fin de l'exécution de la tâche du pool de threads, vous devez appeler awaitTerminationActively call.

  • Attendez que toutes les tâches soumises (y compris l'exécution et l'attente dans la file d'attente) soient exécutées ;
  • Attendez que le délai expire ;
  • le fil est interrompu, jette InterruptedException;

Si l'exécution de la tâche du pool de threads se termine, awaitTerminationla méthode retournera true, sinon elle retournera lorsque le temps d'attente dépassera le temps spécifié false.

// 关闭线程池的钩子函数
private static void shutdown(ExecutorService executorService) {
    
    
    // 第一步:使新任务无法提交
    executorService.shutdown();
    try {
    
    
        // 第二步:等待未完成任务结束
        if(!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
    
    
             // 第三步:取消当前执行的任务
            executorService.shutdownNow();
            // 第四步:等待任务取消的响应
            if(!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
    
    
                System.err.println("Thread pool did not terminate");
            }
        }
    } catch(InterruptedException ie) {
    
    
        // 第五步:出现异常后,重新取消当前执行的任务
        executorService.shutdownNow();
        Thread.currentThread().interrupt(); // 设置本线程中断状态
    }
}

autre

J'ai l'impression que le contenu est tellement important que je ne peux pas le finir ~ !

Lorsque nous parlons du pool de threads, nous devons parler des opérations simultanées multithreads, 同步, 异步, CSA, AQS, 公平锁和非公平锁, 可重入锁和非可重入锁et d'autres points de connaissance requis pour le contrôle de la concurrence.

Il est rarement utilisé dans le travail quotidien Avez-vous une structure de connaissances systématique ? Cela conduit à oublier après avoir beaucoup appris, puis à apprendre et à oublier à nouveau.

J'espère que j'aurai l'opportunité d'apprendre et de partager pas à pas dans le futur.

L'article est tout raconté ici, si vous avez d'autres besoins de communication, vous pouvez laisser un message~!

Je suppose que tu aimes

Origine blog.csdn.net/stven_king/article/details/113870331
conseillé
Classement