Maison  >  Article  >  Java  >  Une explication détaillée de l'utilisation et des principes des pools de threads en Java

Une explication détaillée de l'utilisation et des principes des pools de threads en Java

黄舟
黄舟original
2017-10-09 09:58:261524parcourir

Cet article présente principalement des informations pertinentes sur l'utilisation et les principes du pool de threads Java. Il a une certaine valeur de référence. Les amis intéressés peuvent se référer à

Qu'est-ce qu'un pool de threads ?

Nous pouvons utiliser Java pour créer facilement un nouveau fil, et le système d'exploitation coûte également beaucoup d'argent pour créer un fil. Par conséquent, basé sur la réutilisation des threads, le concept de pool de threads est proposé. Nous utilisons le pool de threads pour créer plusieurs threads après avoir exécuté une tâche, le thread existera pendant un certain temps (l'utilisateur peut définir le temps de survie de). le thread inactif, qui sera abordé plus tard). Lorsqu'une nouvelle tâche arrive, le thread inactif sera directement réutilisé, éliminant ainsi la perte de création et de destruction de threads. Bien sûr, les threads inactifs seront également un gaspillage de ressources (seul le temps de survie des threads inactifs est limité), mais c'est bien mieux que de créer et de détruire fréquemment des threads.
Ce qui suit est mon code de test


  /*
   * @TODO 线程池测试
   */
  @Test
  public void threadPool(){

    /*java提供的统计线程运行数,一开始设置其值为50000,每一个线程任务执行完
     * 调用CountDownLatch#coutDown()方法(其实就是自减1)
     * 当所有的线程都执行完其值就为0
    */
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    Executor pool = Executors.newFixedThreadPool(10);//开启线程池最多会创建10个线程
    for(int i=0;i<50000;i++){
      pool.execute(new Runnable() {
        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50个线程都执行完了,共用时:"+(end-start)+"ms");
  }


  /**
   *@TODO 手动创建线程测试 
   */
  @Test
  public void thread(){
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    for(int i=0;i<50000;i++){
      Thread thread = new Thread(new Runnable() {

        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
      thread.start();
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50000个线程都执行完了,共用时:"+(end-start)+"ms");


  }

Il faut environ 400 ms pour s'exécuter avec 5w threads dans le pool de threads, et il faut environ 4350 ms pour s'exécuter sans utiliser le pool de threads. Son efficacité est visible ( Les lecteurs peuvent le tester par eux-mêmes, mais en raison des différentes configurations informatiques, les données épuisées seront différentes, mais l'utilisation du pool de threads est nettement plus rapide que la création de threads).

Comment Java utilise-t-il le pool de threads ?

Le pool de threads a été utilisé dans le code de test ci-dessus, et il sera officiellement présenté ci-dessous.

Le niveau supérieur de tous les pools de threads Java est une interface Executor, qui n'a qu'une seule méthode d'exécution, qui est utilisée pour effectuer toutes les tâches. Java fournit également l'interface ExecutorService, qui hérite d'Executor et étend les méthodes. Ce qui suit est AbstractExecutorService est une classe abstraite qui implémente ExecutorService. Enfin, ThreadPoolExecutor hérite de la classe abstraite ci-dessus. Le pool de threads Java que nous utilisons souvent est une instance créée de cette classe.

Les Executors que nous avons utilisés ci-dessus sont une classe d'outils. Il s'agit d'un sucre syntaxique qui encapsule pour nous les paramètres du pool de threads de diverses entreprises et effectue de nouvelles opérations.


 public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                   0L, TimeUnit.MILLISECONDS,
                   new LinkedBlockingQueue<Runnable>());
  }

Ce qui précède est le code source d'Executors.newFixedThreadPool(10).

Ce qui suit est le point clé. Parlons de la signification de chaque paramètre de la méthode de construction ThreadPoolExecutor.


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

La méthode de construction ci-dessus est la plus complète.

Ci-dessous nous expliquerons la signification de certains paramètres basés sur le code source, ce qui sera plus convaincant.

Ce qui suit est la méthode ThreadPoolExecutor#execute, qui est le véritable exécuteur d'exécution appelé par l'interface ci-dessus.


 public void execute(Runnable command) {
    if (command == null)
      throw new NullPointerException();

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
      if (addWorker(command, true))
        return;
      c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
      int recheck = ctl.get();
      if (! isRunning(recheck) && remove(command))
        reject(command);
      else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
    }
    else if (!addWorker(command, false))
      reject(command);
  }

ctl est une instance d'AtomicInteger, une classe qui fournit des opérations CAS pour les instructions atomiques. Elle est utilisée pour enregistrer le nombre de threads actuellement en cours d'exécution dans le pool de threads, plus. -2^29, la méthode workCountOf obtient sa valeur absolue (vous pouvez voir comment l'implémenter dans le code source). Lorsqu'elle est inférieure à corePoolSize, la méthode addWorker sera appelée (elle est utilisée pour créer un nouveau Worker, et). le Worker créera un Thread, donc il crée une méthode thread.), addWorkd le comparera avec la valeur de corePoolSize ou maxnumPoolSize lors de la création du thread (lorsque true est transmis, il sera comparé en fonction de corePoolSize, false sera comparé en fonction de sur maxnumPoolSize, et si la valeur est supérieure ou égale à sa valeur, la création échouera). On peut voir que si le nombre de threads en cours d'exécution est inférieur à corePoolSize, il est créé et sera créé avec succès (
ne discute que brièvement du pool de threads à l'état d'exécution).

Si le nombre de threads en cours d'exécution est supérieur ou égal à corePoolSize, la saisie du deuxième if, isRunning est comparée à SHUTDOWN (sa valeur = 0, comme mentionné précédemment, c est égal au nombre de threads en cours d'exécution). threads plus -2 ^ 29. Si les données du thread en cours d'exécution atteignent 2 ^ 29, sa valeur = 0, isRunning renvoie false et l'exécution de addWorkd dans else renverra également false (addWorkd le vérifie également), cela signifie donc que le pool de threads peut prendre en charge 2 ^ 29 threads exécutés simultanément (assez).

workQueue.offer(command) consiste à ajouter l'exécutable à la file d'attente. Après avoir rejoint la file d'attente, la méthode runWorker obtiendra l'exécution de la tâche à partir de la file d'attente. Si la file d'attente actuelle utilise une file d'attente limitée (ArrayBlockingQueue), lorsque la file d'attente est pleine, l'offre renverra false. Cela entrera le else if! False est transmis ici, indiquant qu'il est comparé à maxnumPoolSize Si le thread. exécuté ici Le nombre est supérieur ou égal à maxnumPoolSize, alors cette tâche de thread sera rejetée par le pool de threads et exécutera rejet(commande). La méthode de rejet utilise le RejectedExecutionHandler (stratégie de rejet) dans notre méthode de construction ThreadPoolExecutor, qui sera expliquée. en détail plus tard.

Après l'introduction ci-dessus combinée au code source, l'introduction suivante aux paramètres de notre ThreadPoolExecutor sera facile à comprendre.

Les stratégies de création et de rejet de threads dans le pool de threads

corePoolSize, maxnumPoolSize et BlockingQueue doivent être discutées ensemble

Lorsque les threads exécutés dans le pool de threads sont inférieurs à corePoolSize, une nouvelle tâche de thread créera toujours un nouveau thread à exécuter ; lorsqu'elle est supérieure à corePoolSize, la tâche sera ajoutée à la file d'attente blockingQueue. vous transmettez est une file d'attente illimitée (LinkedBlockingQueue). Il s'agit d'une file d'attente qui peut stocker "une infinité" de tâches. Toutes seront toujours ajoutées à la file d'attente avec succès. Cela n'a rien à voir avec maxnumPoolSize. de threads dans le pool de threads est corePoolSize ; mais si vous passez dans la file d'attente limitée (ArrayBlockingQueue, SynchronousQueue), lorsque la file d'attente est pleine et que le nombre de threads est inférieur à maxmunPoolSize, de nouveaux threads sont créés jusqu'à ce que le nombre de threads soit supérieur à maxnumPoolSize. ; si le nombre de threads est supérieur à maxnumPoolSize, la tâche de jointure sera rejetée par le pool de threads.

Stratégie de rejet RejectedExecutionHandler Java implémente 4 AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy Les utilisateurs peuvent également implémenter cette interface pour implémenter leur propre stratégie de rejet ; pour exécuter directement la nouvelle tâche ; la troisième consiste à annuler la tâche la plus ancienne de la file d'attente et la quatrième consiste à annuler la tâche en cours ;

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

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