Maison  >  Article  >  Java  >  Une brève introduction aux pools de threads dans la programmation Java

Une brève introduction aux pools de threads dans la programmation Java

高洛峰
高洛峰original
2017-02-07 15:06:151313parcourir

À partir de Java 5, Java fournit son propre pool de threads. Un pool de threads est un conteneur de threads qui exécute uniquement un nombre nominal de threads à la fois. java.util.concurrent.ThreadPoolExecutor est un tel pool de threads. Il est très flexible, mais aussi compliqué à utiliser. Cet article en donnera une introduction.

Le premier est le constructeur. Prenons l'exemple du constructeur le plus simple :

public ThreadPoolExecutor(
      int corePoolSize,
      int maximumPoolSize,
      long keepAliveTime,
      TimeUnit unit,
      BlockingQueue<Runnable> workQueue)

semble assez compliqué. Voici une introduction.
corePoolSize fait référence à la taille du pool de threads réservé.
maximumPoolSize fait référence à la taille maximale du pool de threads.
keepAliveTime fait référence au délai d'expiration nécessaire à la fin du thread inactif.
unit est une énumération représentant l'unité de keepAliveTime.
workQueue représente la file d'attente qui stocke les tâches.
Nous pouvons comprendre la signification de ces paramètres à partir du processus de travail du pool de threads. Le processus de fonctionnement du pool de threads est le suivant :

1. Lorsque le pool de threads est créé pour la première fois, il ne contient aucun thread. La file d'attente des tâches est transmise en paramètre. Cependant, même s'il y a des tâches dans la file d'attente, le pool de threads ne les exécutera pas immédiatement.

2. Lors de l'appel de la méthode execute() pour ajouter une tâche, le pool de threads fera le jugement suivant :
a. Si le nombre de threads en cours d'exécution est inférieur à corePoolSize, créez immédiatement un thread. pour exécuter la tâche ;
b. Si le nombre de threads en cours d'exécution est supérieur ou égal à corePoolSize, alors mettez cette tâche dans la file d'attente.
c. Si la file d'attente est pleine à ce moment-là et que le nombre de threads en cours d'exécution est inférieur à maximumPoolSize, alors vous devez toujours créer un thread pour exécuter la tâche
d. le nombre de threads en cours d'exécution est supérieur ou égal à maximumPoolSize , le pool de threads lèvera une exception et indiquera à l'appelant "Je ne peux plus accepter de tâches".

3. Lorsqu'un thread termine une tâche, il prend une tâche de la file d'attente pour l'exécuter.

4. Lorsqu'un thread n'a rien à faire et dépasse un certain temps (keepAliveTime), le pool de threads déterminera que si le nombre de threads en cours d'exécution est supérieur à corePoolSize, alors le thread sera arrêté. Ainsi, une fois toutes les tâches du pool de threads terminées, il finira par être réduit à la taille de corePoolSize.

Ce processus montre que cela ne signifie pas nécessairement que la tâche sera exécutée en premier si elle est ajoutée en premier. Supposons que la taille de la file d'attente est de 10, corePoolSize est de 3 et maximumPoolSize est de 6. Ensuite, lorsque 20 tâches sont ajoutées, l'ordre d'exécution est le suivant : les tâches 1, 2 et 3 sont exécutées en premier, puis les tâches 4 à 13 sont exécutées. mettre dans la file d'attente. À ce stade, la file d'attente est pleine, les tâches 14, 15 et 16 seront exécutées immédiatement et les tâches 17 à 20 lèveront une exception. L'ordre final est : 1, 2, 3, 14, 15, 16, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13. Voici un exemple d'utilisation d'un pool de threads :

public static void main(String[] args) {
  BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
  ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1, TimeUnit.DAYS, queue);
   
  for (int i = 0; i < 20; i++) {
    executor.execute(new Runnable() {
   
      public void run() {
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println(String.format("thread %d finished", this.hashCode()));
      }
    });
  }
  executor.shutdown();
}

La description de cet exemple est la suivante :

1. n'est qu'une interface. Les classes d'implémentation couramment utilisées incluent LinkedBlockingQueue et ArrayBlockingQueue. L’avantage d’utiliser LinkedBlockingQueue est qu’il n’y a pas de limite de taille. Dans ce cas, comme la file d'attente ne sera pas pleine, execute() ne lèvera pas d'exception et le nombre de threads exécutés dans le pool de threads ne dépassera jamais corePoolSize, donc le paramètre keepAliveTime n'aura aucun sens.

2. La méthode shutdown() ne bloque pas. Après avoir appelé la méthode shutdown(), le thread principal se termine immédiatement et le pool de threads continuera à s'exécuter jusqu'à ce que toutes les tâches soient exécutées. Si la méthode shutdown() n'est pas appelée, le pool de threads sera maintenu actif afin que de nouvelles tâches puissent être ajoutées à tout moment.

Jusqu'à présent, nous n'avons introduit qu'une petite partie de ce pool de threads. ThreadPoolExecutor est hautement évolutif, mais la condition préalable pour l'étendre est de se familiariser avec sa méthode de travail. Les articles ultérieurs expliqueront comment étendre la classe ThreadPoolExecutor.

La classe ava.util.concurrent.ThreadPoolExecutor offre une riche extensibilité. Vous pouvez personnaliser son comportement en le sous-classant. Par exemple, je souhaite imprimer un message après la fin de chaque tâche, mais je ne peux pas modifier l'objet de la tâche, alors je peux écrire comme ceci :

ThreadPoolExecutor executor = new ThreadPoolExecutor(size, maxSize, 1, TimeUnit.DAYS, queue) {
  @Override
  protected void afterExecute(Runnable r, Throwable t) {
    System.out.println("Task finished.");
  }
};

En plus de la méthode afterExecute, la classe ThreadPoolExecutor a également Les méthodes beforeExecute() et terminated() peuvent être remplacées pour être exécutées respectivement avant l'exécution de la tâche et après l'arrêt de l'ensemble du pool de threads.


除了可以添加任务执行前后的动作之外, ThreadPoolExecutor 还允许你自定义当添加任务失败后的执行策略。你可以调用线程池的 setRejectedExecutionHandler() 方法,用自定义的 RejectedExecutionHandler 对象替换现有的策略。 ThreadPoolExecutor 提供 4 个现有的策略,分别是:
ThreadPoolExecutor.AbortPolicy:表示拒绝任务并抛出异常
ThreadPoolExecutor.DiscardPolicy:表示拒绝任务但不做任何动作
ThreadPoolExecutor.CallerRunsPolicy:表示拒绝任务,并在调用者的线程中直接执行该任务
ThreadPoolExecutor.DiscardOldestPolicy:表示先丢弃任务队列中的第一个任务,然后把这个任务加进队列。
这里是一个例子:
ThreadPoolExecutor executor = new ThreadPoolExecutor(size, maxSize, 1, TimeUnit.DAYS, queue);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());

除此之外,你也可以通过实现 RejectedExecutionHandler 接口来编写自己的策略。下面是一个例子:

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1, TimeUnit.SECONDS, queue,
    new RejectedExecutionHandler() {
      public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println(String.format("Task %d rejected.", r.hashCode()));
      }
    }
);

更多简单介绍Java编程中的线程池相关文章请关注PHP中文网!


Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn