Maison >Java >javaDidacticiel >Plusieurs méthodes d'implémentation du pool de threads Java et réponses aux questions courantes

Plusieurs méthodes d'implémentation du pool de threads Java et réponses aux questions courantes

黄舟
黄舟original
2017-01-20 11:15:171442parcourir

L'éditeur suivant vous proposera plusieurs méthodes d'implémentation et réponses aux questions fréquemment posées sur les pools de threads Java. L'éditeur le trouve plutôt bien, j'aimerais donc le partager avec vous maintenant et le donner comme référence. Suivons l'éditeur et jetons un coup d'œil

Les fils de discussion sont souvent impliqués dans le travail. Par exemple, certaines tâches sont souvent confiées à des threads pour une exécution asynchrone. Ou bien, le programme serveur crée une tâche de traitement de thread distincte pour chaque requête. En dehors des threads, comme la connexion à la base de données que nous utilisons. Ces opérations de création, de destruction ou d'ouverture et de fermeture affectent grandement les performances du système. Par conséquent, l’utilité du « pool » est mise en avant.

1. Pourquoi utiliser le pool de threads

Dans l'implémentation introduite dans la section 3.6.1, un nouveau thread de travail est alloué à chaque client. Lorsque la communication entre le thread de travail et le client se termine, ce thread est détruit. Cette implémentation présente les inconvénients suivants :

• La surcharge du travail de création et de destruction du serveur (y compris le temps et les ressources système dépensés) est importante. Il n'est pas nécessaire d'expliquer cet élément. Vous pouvez vérifier le "Processus de création de fil". En plus du travail effectué par la machine elle-même, nous devons également instancier et démarrer, ce qui nécessite tous des ressources de pile.

• En plus de la surcharge liée à la création et à la destruction des threads, les threads actifs consomment également des ressources système. Cela devrait être une consommation de ressources de pile. Je suppose que définir une valeur raisonnable pour le nombre de connexions à la base de données est également une considération.

•Si le nombre de threads est fixe et que chaque thread a un long cycle de vie, la commutation de thread est également relativement fixe. Différents systèmes d'exploitation ont des cycles de commutation différents, généralement d'environ 20 ms. La commutation mentionnée ici est le transfert des droits d'utilisation du processeur entre les threads sous la planification de la JVM et du système d'exploitation sous-jacent. Si les threads sont fréquemment créés et détruits, les threads seront changés fréquemment, car après la destruction d'un thread, les droits d'utilisation doivent être cédés au thread qui est déjà prêt, afin que le thread puisse avoir une chance de s'exécuter. Dans ce cas, la commutation entre les threads ne suit plus le cycle de commutation fixe du système, et le coût du changement de thread est encore plus élevé que le coût de création et de destruction.

Relativement parlant, lors de l'utilisation d'un pool de threads, certains threads seront pré-créés, et ils retireront continuellement des tâches de la file d'attente de travail, puis exécuteront les tâches. Lorsque le thread de travail termine l'exécution d'une tâche, il continue d'exécuter une autre tâche dans la file d'attente de travail. Les avantages sont les suivants :

• Réduit le nombre de créations et de destructions. Chaque thread de travail peut être réutilisé à tout moment et peut effectuer plusieurs tâches.

•Vous pouvez facilement ajuster le nombre de threads dans le pool de threads en fonction de la capacité de charge du système pour éviter les pannes du système dues à une consommation excessive de ressources système.

2. Implémentation simple du pool de threads

Ce qui suit est un pool de threads simple écrit par moi-même, qui est également copié directement du livre Java Network Programming

package thread;
 
import java.util.LinkedList;
 
/**
 * 线程池的实现,根据常规线程池的长度,最大长度,队列长度,我们可以增加数目限制实现
 * @author Han
 */
public class MyThreadPool extends ThreadGroup{
  //cpu 数量 ---Runtime.getRuntime().availableProcessors();
  //是否关闭
  private boolean isClosed = false;
  //队列
  private LinkedList<Runnable> workQueue;
  //线程池id
  private static int threadPoolID;
  private int threadID;
  public MyThreadPool(int poolSize){
    super("MyThreadPool."+threadPoolID);
    threadPoolID++;
    setDaemon(true);
    workQueue = new LinkedList<Runnable>();
    for(int i = 0;i<poolSize;i++){
      new WorkThread().start();
    }
  }
  //这里可以换成ConcurrentLinkedQueue,就可以避免使用synchronized的效率问题
  public synchronized void execute(Runnable task){
    if(isClosed){
      throw new IllegalStateException("连接池已经关闭...");
    }else{
      workQueue.add(task);
      notify();
    }
  }
   
  protected synchronized Runnable getTask() throws InterruptedException {
    while(workQueue.size() == 0){
      if(isClosed){
        return null;
      }
      wait();
    }
    return workQueue.removeFirst();
  }
   
  public synchronized void close(){
    if(!isClosed){
      isClosed = true;
      workQueue.clear();
      interrupt();
    }
  }
   
  public void join(){
    synchronized (this) {
      isClosed = true;
      notifyAll();
    }
    Thread[] threads = new Thread[activeCount()];
    int count = enumerate(threads);
    for(int i = 0;i<count;i++){
      try {
        threads[i].join();
      } catch (Exception e) {
      }
    }
  }
   
  class WorkThread extends Thread{
    public WorkThread(){
      super(MyThreadPool.this,"workThread"+(threadID++));
      System.out.println("create...");
    }
    @Override
    public void run() {
      while(!isInterrupted()){
        System.out.println("run..");
        Runnable task = null;
        try {
          //这是一个阻塞方法
          task = getTask();
           
        } catch (Exception e) {
           
        }
        if(task != null){
          task.run();
        }else{
          break;
        }
      }
    }
  }
}

Ce pool de threads définit principalement une file d'attente de travail et quelques threads pré-créés. Tant que la méthode d'exécution est appelée, la tâche peut être soumise au thread.

Lorsqu'il n'y a pas de tâche, le thread suivant se bloquera dans getTask() jusqu'à ce qu'une nouvelle tâche arrive et soit réveillée.

Join et close peuvent être utilisés pour fermer le pool de threads. La différence est que join terminera l'exécution des tâches dans la file d'attente, tandis que close effacera immédiatement la file d'attente et interrompra tous les threads de travail. L'interruption() dans close() équivaut à appeler l'interruption() respective des threads enfants dans le ThreadGroup, donc lorsqu'un thread est en attente ou en veille, une InterruptException sera levée

La classe de test est comme suit :

public class TestMyThreadPool {
  public static void main(String[] args) throws InterruptedException {
    MyThreadPool pool = new MyThreadPool(3);
    for(int i = 0;i<10;i++){
      pool.execute(new Runnable() {
        @Override
        public void run() {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
          }
          System.out.println("working...");
        }
      });
    }
    pool.join();
    //pool.close();
  }
}

3. Le pool de threads fourni par la bibliothèque de classes jdk

java fournit une bonne implémentation de pool de threads, qui est plus robuste et efficace que notre propre implémentation, et est aussi plus puissant.

Le diagramme de classes est le suivant :

Les seniors ont déjà donné de bonnes explications sur ce type de pool de threads. Tout pool de threads Java sous Baidu contient des exemples et des didacticiels très détaillés, je n'entrerai donc pas dans les détails ici.

4. Pool de threads d'injection Spring

Lors de l'utilisation du framework Spring, si nous utilisons la méthode fournie par Java pour créer un pool de threads, il sera très gênant à gérer dans des applications multithreads. , et ce n'est pas conforme. Nous utilisons des idées printanières. (Bien que Spring puisse être injecté via des méthodes statiques)

En fait, Spring lui-même fournit également une bonne implémentation de pool de threads. Cette classe s'appelle ThreadPoolTaskExecutor.

La configuration au printemps est la suivante :

<bean id="executorService" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="${threadpool.corePoolSize}" />
    <!-- 线程池维护线程的最少数量 -->
    <property name="keepAliveSeconds" value="${threadpool.keepAliveSeconds}" />
    <!-- 线程池维护线程所允许的空闲时间 -->
    <property name="maxPoolSize" value="${threadpool.maxPoolSize}" />
    <!-- 线程池维护线程的最大数量 -->
    <property name="queueCapacity" value="${threadpool.queueCapacity}" />
    <!-- 线程池所使用的缓冲队列 -->
  </bean>

5. Précautions d'utilisation du pool de threads

•Deadlock

Tout programme multithread le fera. Il existe un risque de blocage. La situation la plus simple est celle de deux threads AB, A détient le verrou 1 et demande le verrou 2, et B détient le verrou 2 et demande le verrou 1. (Cette situation se produira également dans le verrouillage exclusif de mysql, et la base de données signalera directement un message d'erreur). Il existe un autre type de blocage dans le pool de threads : en supposant que tous les threads de travail du pool de threads sont bloqués lors de l'exécution de leurs tâches respectives, ils attendent le résultat de l'exécution d'une certaine tâche A. Cependant, la tâche A est dans la file d'attente et ne peut pas être exécutée car il n'y a pas de thread inactif. De cette façon, toutes les ressources du pool de threads seront bloquées pour toujours et un blocage se produira.

•Ressources système insuffisantes

Si le nombre de threads dans le pool de threads est très important, ces threads consommeront une grande quantité de ressources, notamment de la mémoire et d'autres ressources système, affectant ainsi sérieusement les performances du système. .

•Erreur de concurrence

La file d'attente de travail du pool de threads s'appuie sur les méthodes wait() et notify() pour permettre au thread de travail d'obtenir les tâches à temps, mais ces deux méthodes sont difficiles à utiliser. Si le code est erroné, les notifications peuvent être perdues, ce qui fait que le thread de travail reste inactif, ignorant les tâches qui doivent être traitées dans la file d'attente de travail. Parce qu'il est préférable d'utiliser des pools de threads plus matures.

• Fuites de threads

Les fuites de threads constituent un risque sérieux lié à l'utilisation de pools de threads. Pour un pool de threads avec un nombre fixe de threads de travail, si un thread de travail renvoie une RuntimeException ou une erreur lors de l'exécution d'une tâche et que ces exceptions ou erreurs ne sont pas interceptées, le thread de travail se terminera anormalement, entraînant la perte permanente du pool de threads. un fil. (C'est tellement intéressant)

Une autre situation est que le thread de travail est bloqué lors de l'exécution d'une tâche. S'il attend que l'utilisateur saisisse des données, mais que l'utilisateur n'a pas saisi de données, le thread a été bloqué. . Un tel thread de travail n’existe que de nom et n’effectue en réalité aucune tâche. Si tous les threads du pool de threads sont dans cet état, le pool de threads ne peut pas ajouter de nouvelles tâches.

•Surcharge de tâches

Lorsqu'il y a un grand nombre de tâches en file d'attente pour exécution dans la file d'attente des threads de travail, ces tâches elles-mêmes peuvent consommer trop de ressources système et provoquer une pénurie de ressources.

Pour résumer, lorsque vous utilisez le pool de threads, vous devez suivre les principes suivants :

1. Si la tâche A doit attendre de manière synchrone le résultat de l'exécution de la tâche B pendant l'exécution, alors la tâche A ne convient pas Rejoignez la file d'attente de travail du pool de threads. Si vous ajoutez à la file d'attente des tâches comme la tâche A qui doivent attendre les résultats d'exécution d'autres tâches, cela peut provoquer une impasse

2 Si l'exécution d'une certaine tâche peut être bloquée et qu'elle est bloquée pendant un certain temps. pendant une longue période, vous devez définir un délai d'attente pour éviter que le thread de travail ne soit bloqué de manière permanente et ne provoque une fuite de thread. Dans le programme serveur, lorsque le thread attend la connexion du client, ou attend les données envoyées par le client, cela peut provoquer un blocage. Vous pouvez régler l'heure de la manière suivante :

Appelez setSotimeout. méthode de ServerSocket pour définir le délai d’attente avant que le client ne se connecte.

Pour chaque socket connecté au client, appelez la méthode setSoTImeout du socket pour définir le délai d'attente pour que le client envoie des données.

3. Comprendre les caractéristiques de la tâche et analyser si la tâche effectue des opérations io qui bloquent souvent ou effectue des opérations qui ne bloquent jamais. Le premier occupe le processeur par intermittence, tandis que le second a une utilisation plus élevée. Estimez le temps qu'il faudra pour terminer la tâche, qu'il s'agisse d'une tâche à court terme ou à long terme, puis classez les tâches en fonction des caractéristiques des tâches, puis ajoutez différents types de tâches aux files d'attente de travail. de différents pools de threads, afin qu'ils puissent être traités en fonction des caractéristiques des tâches, Caractéristiques des tâches, allocation et ajustement de chaque pool de threads

4. Ajuster la taille du pool de threads. La taille optimale du pool de threads dépend principalement du nombre de processeurs disponibles dans le système et des caractéristiques des tâches dans la file d'attente de travail. S'il n'y a qu'une seule file d'attente de travail sur un système avec N processeurs et que tous sont des tâches de calcul (non bloquantes), alors lorsque le pool de threads a N ou N 1 threads de travail, l'utilisation maximale du processeur sera généralement obtenue.

Si la file d'attente de travail contient des tâches qui effectuent des opérations d'E/S et bloquent fréquemment, la taille du pool de threads doit dépasser le nombre de processeurs disponibles, car tous les threads de travail ne fonctionnent pas tout le temps. Sélectionnez une tâche typique, puis estimez le rapport WT/ST du temps d'attente par rapport au temps réel occupé par le processeur pour les calculs dans le projet qui effectue cette tâche. Pour un système avec N processeurs, environ N*(1 WT/ST) threads doivent être configurés pour garantir que le processeur est pleinement utilisé.

Bien sûr, l'utilisation du processeur n'est pas la seule chose à prendre en compte lors de l'ajustement du pool de threads. À mesure que le nombre de tâches du pool de threads augmente, vous rencontrerez également des limitations en termes de mémoire ou d'autres ressources, telles que les sockets, les fichiers ouverts. gérer ou nombre de connexions à la base de données, etc. Il est nécessaire de s'assurer que les ressources système consommées par plusieurs threads se situent dans la plage que le système peut supporter.

5. Évitez la surcharge de tâches. Le serveur doit limiter le nombre de connexions client simultanées en fonction de la capacité de charge du système. Lorsque la connexion du client dépasse la limite, le serveur peut rejeter la connexion et envoyer une invite conviviale, ou limiter la longueur de la file d'attente.

Ce qui précède présente plusieurs méthodes d'implémentation du pool de threads Java et le contenu des FAQ. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !


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