Explication détaillée du pool de fils et résolution des problèmes de cheveux

Cet article a été inclus dans la colonne
"Java"

note conceptuelle

Qu'est-ce qu'un pool de threads

  Un pool de threads est un mécanisme de gestion et de réutilisation des threads. Il se compose d'une file d'attente de threads et d'un ensemble de méthodes de gestion des threads. Les threads du pool de threads peuvent être réutilisés pour exécuter des tâches soumises sans créer ni détruire de threads à chaque fois.

Composants du pool de threads

  File d'attente de threads : le pool de threads maintient une file d'attente de threads pour stocker les tâches à exécuter. Lorsqu'une tâche est soumise au pool de threads, le pool de threads prend un thread inactif de la file d'attente pour exécuter la tâche.
  Gestionnaire de threads : le gestionnaire de threads du pool de threads est responsable de la création, du démarrage et de l'arrêt des threads. Il crée dynamiquement des threads selon les besoins et les ajoute à la file d'attente des threads. Lorsqu'un thread n'est plus nécessaire au pool de threads, le gestionnaire de threads arrête le thread et le supprime de la file d'attente des threads.
  Interface de tâche : l'interface de tâche dans le pool de threads définit le type de tâche à exécuter. Une tâche peut être une tâche normale qui implémente l'interface Runnable ou une tâche avec une valeur de retour qui implémente l'interface Callable.
  File d'attente des tâches : la file d'attente des tâches du pool de threads est utilisée pour stocker les tâches à exécuter. Lorsqu'une tâche est soumise au pool de threads, le pool de threads ajoute la tâche à la file d'attente des tâches et attend que le thread s'exécute.
insérez la description de l'image ici

Avantages et inconvénients

Avantages du pool de threads

  • Réduire la consommation de ressources : le pool de threads peut réutiliser les threads créés, évitant ainsi les frais généraux liés à la création et à la destruction fréquentes de threads. La création et la destruction de threads doivent consommer des ressources système, y compris le processeur et la mémoire. En utilisant le pool de threads, la création et la destruction de threads peuvent être concentrées en un seul endroit, ce qui réduit la consommation de ressources et améliore les performances et l'efficacité du système.
  • Améliorer la vitesse de réponse : le pool de threads peut améliorer la vitesse de réponse des tâches. Les threads du pool de threads sont pré-créés. Lorsqu'une tâche arrive, un thread inactif peut être alloué immédiatement pour exécuter la tâche sans attendre que le thread soit créé et démarré. Cela peut réduire le temps d'attente de la tâche et améliorer la vitesse de réponse de la tâche.
  • Contrôlez le nombre de threads simultanés : le pool de threads peut contrôler le nombre de threads simultanés, évitant ainsi la concurrence des ressources et la dégradation des performances causées par un trop grand nombre de threads dans le système. En définissant la taille du pool de threads, le nombre de threads exécutés simultanément peut être limité pour éviter une surcharge du système. Dans le même temps, le pool de threads peut également ajuster dynamiquement le nombre de threads en fonction de la charge du système pour maintenir la stabilité et les performances du système.
  • Fournir des fonctions de gestion et de surveillance des threads : le pool de threads fournit certaines méthodes de gestion et de surveillance des threads, qui peuvent mieux contrôler et optimiser l'exécution des threads. Par exemple, des attributs tels que la priorité du thread, le délai d'attente et le nom du thread peuvent être définis. Dans le même temps, le pool de threads fournit également certaines méthodes de surveillance, qui peuvent obtenir des informations telles que l'état et l'état d'exécution des threads dans le pool de threads, afin de faciliter le débogage et l'optimisation des threads.

Inconvénients du pool de threads

  • Occupation des ressources : le pool de threads pré-créera un certain nombre de threads, et ces threads existeront toujours, même s'il n'y a pas de tâches à exécuter. Cela occupera certaines ressources système, telles que la mémoire et les ressources CPU.
    Exemple : supposons qu'un pool de threads soit configuré avec 10 threads, mais que seules 2 tâches doivent être exécutées dans un certain laps de temps, et les 8 autres threads seront toujours inactifs et occuperont des ressources.
  • Fuite de thread : les threads du pool de threads peuvent être réutilisés. Lorsqu'une tâche est exécutée, le thread retourne dans le pool de threads et est marqué comme disponible. Cependant, si une exception se produit pendant l'exécution de la tâche et que le thread n'est pas renvoyé correctement au pool de threads, cela entraînera une fuite de thread.
    Exemple : Une exception s'est produite lors de l'exécution d'une tâche et le thread n'a pas été renvoyé au pool de threads. De cette manière, le thread ne peut pas être réutilisé et occupera toujours des ressources système.
  • Problème de blocage : les threads du pool de threads sont limités. Lorsqu'il y a trop de tâches, le pool de threads peut ne pas être en mesure de traiter les tâches à temps en raison d'un nombre insuffisant de threads, ce qui entraîne le blocage des tâches.
    Exemple : il n'y a que 5 threads dans le pool de threads, mais 10 tâches doivent être exécutées en même temps. Les 5 premières tâches peuvent être exécutées immédiatement, mais les 5 tâches suivantes doivent attendre que l'exécution des tâches précédentes soit terminée avant de pouvoir être exécutées, ce qui entraîne le blocage des tâches.

application

Créer un pool de threads

6 façons de créer un pool de threads
insérez la description de l'image ici

Créer des paramètres de pool de threads
insérez la description de l'image ici

soumettre la tâche

Deux façons de soumettre des tâches

1.executor : la soumission d'une tâche exécutable consiste à soumettre une tâche sans valeur de retour au pool de threads

Classe affaires spécifique

public class TaskToRunnable  implements Runnable{
    
    

    @Override
    public void run() {
    
    
        //打印线程的名字
        System.out.println("线程名:"+Thread.currentThread().getName());
    }
}

client

public class Client {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        //创建任务无返回值
        TaskToRunnable taskToRunnable=new TaskToRunnable();
        //创建单个线程的线程池
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //提交任务
        threadPool.execute(taskToRunnable);
        //关闭线程池
        threadPool.shutdown();
    }
}

résultat de l'opération
insérez la description de l'image ici

2.submit : soumettre une tâche appelable, qui consiste à soumettre une tâche avec une valeur de retour au pool de threads

Classe affaires spécifique

public class TaskToCallable implements Callable<Integer> {
    
    
    @Override
    public Integer call() {
    
    
        return 5+5;
    }
}

client

public class Client {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    

        //创建任务有返回值
        TaskToCallable taskToCallable=new TaskToCallable();
        //创建单个线程的线程池
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //提交任务
        Future<Integer> submit = threadPool.submit(taskToCallable);
        try{
    
    
            System.out.println("返回值为:"+submit.get());
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            //关闭线程池
            threadPool.shutdown();
        }
    }
}

résultat de l'opération
insérez la description de l'image ici

annuler la tâche

L'annulation de tâches est divisée en trois phases : annulation des tâches non exécutées, annulation des tâches en cours d'exécution et annulation des tâches terminées. Ce qui suit vous montrera leurs effets sous forme de code.

Annuler les tâches non exécutées

code client

/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.cancel
 * @Author: Wuzilong
 * @Description: 取消未执行的任务
 * @CreateTime: 2023-07-23 23:42
 * @Version: 1.0
 */

public class UnExecutedClient {
    
    
    public static void main(String[] args) {
    
    
        TaskToCallable taskToCallable=new TaskToCallable();
        ExecutorService threadExecutor= Executors.newSingleThreadExecutor();
        Future<Integer> future1 = threadExecutor.submit(taskToCallable);
        Future<Integer> future2 = threadExecutor.submit(taskToCallable);
        boolean cancel = future2.cancel(false);
        System.out.println("任务2是否取消成功"+cancel);
        try{
    
    
            Integer integer = future2.get();
            System.out.println("任务2的返回值"+integer);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            threadExecutor.shutdown();
        }
    }
}

Les trois exemples de code métier sont les mêmes, les deux exemples suivants ne sont plus écrits.

/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.commit
 * @Author: Wuzilong
 * @Description: 业务代码
 * @CreateTime: 2023-07-23 22:58
 * @Version: 1.0
 */

public class ExecuteClientTask implements Callable<Integer> {
    
    
    @Override
    public Integer call() throws Exception {
    
    
        int i=0;
        //线程没有被中断递增i
        while(!Thread.interrupted()){
    
    
            i++;
        }
        System.out.println("当前i的值为:"+i);
        return 5+5;
    }
}

résultat de l'opération
insérez la description de l'image ici

  Nous avons créé un pool de threads avec un seul thread et créé deux threads. Un tel thread est dans la file d'attente des threads et un thread est dans la file d'attente des tâches. Nous annulons le thread dans la file d'attente des tâches et obtenons la valeur du thread annulé. Vous constaterez qu'une erreur sera signalée lors de l'obtention de la valeur, indiquant que le fil a été annulé.

Annuler une tâche en cours

code client


/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.cancel
 * @Author: Wuzilong
 * @Description: 取消执行中的任务
 * @CreateTime: 2023-07-23 23:50
 * @Version: 1.0
 */

public class ExecuteClient {
    
    
    public static void main(String[] args) {
    
    
        ExecuteClientTask taskToCallable=new ExecuteClientTask();
        ExecutorService threadExecutor= Executors.newSingleThreadExecutor();
        Future<Integer> future = threadExecutor.submit(taskToCallable);
        System.out.println("任务是否完成:"+future.isDone());
        //参数为false执行结果显示取消成功但是程序没有结束    任务继续执行完
        //参数为true直接结果显示取消成功程序结束了
        boolean cancel = future.cancel(true);
        System.out.println("任务1是否取消成功"+cancel);
        try{
    
    
            Integer integer = future.get();
            System.out.println("获取任务1的结果:"+integer);

        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            threadExecutor.shutdown();
        }
    }
}

À la suite de l'opération,
insérez la description de l'image ici
  nous avons également créé un pool de threads en tant que thread. Après avoir soumis la tâche, l'opération d'annulation est directement effectuée. Lors de l'exécution de l'opération d'annulation, nous passerons un paramètre, vrai ou faux : le paramètre est faux , et le résultat de l'exécution indique que l'annulation a réussi mais que le programme Si ce n'est pas terminé, la tâche continue de s'exécuter. Le paramètre est vrai et le résultat direct indique que l'annulation a réussi et que le programme est terminé. Dans l'exemple ci-dessus, true est transmis, et la valeur ne peut pas être obtenue ci-dessous, et l'erreur d'annulation de thread sera signalée directement.

Annuler une tâche terminée

code client

/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.cancel
 * @Author: Wuzilong
 * @Description: 取消已完成的任务  无法取消
 * @CreateTime: 2023-07-23 23:54
 * @Version: 1.0
 */

public class ExecutedClient {
    
    
    public static void main(String[] args) {
    
    
        TaskToCallable taskToCallable=new TaskToCallable();
        ExecutorService threadExecutor= Executors.newSingleThreadExecutor();
        Future<Integer> future = threadExecutor.submit(taskToCallable);

        try{
    
    
            Integer integer = future.get();
            System.out.println("获取任务1的结果:"+integer);
            //参数只对运行时的任务有效
            boolean cancel = future.cancel(false);
            System.out.println("任务1是否取消成功"+cancel);
            System.out.println("任务1的返回值"+integer);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            threadExecutor.shutdown();
        }
    }
}

À la suite de l'opération,
insérez la description de l'image ici
  nous avons également créé un pool de threads en tant que thread. Après avoir soumis la tâche, nous avons obtenu le résultat de retour de la tâche, puis nous avons annulé la tâche. On constate que la valeur de retour de la tâche peut encore être obtenue après annulation, donc la tâche qui a été exécutée ne peut pas être annulée.

insérez la description de l'image ici

rejeter la tâche

Logique métier

public class Task implements Runnable{
    
    
    private final int index;

    public Task(int index){
    
    
        this.index=index;
    }

    @Override
    public void run() {
    
    
        System.out.println("输出线程名:"+Thread.currentThread().getName()+"线程编号:"+index);
    }
}

client

/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.refuse
 * @Author: Wuzilong
 * @Description: 线程池拒绝任务
 * @CreateTime: 2023-07-24 01:08
 * @Version: 1.0
 */

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //直接丢弃,只有前两个线程
//        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.DiscardPolicy());
        //拒绝的任务交个调用者的线程去执行
//        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.CallerRunsPolicy());
        //默认的拒绝策略,直接抛出异常信息
//        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.AbortPolicy());
        //丢弃队列头部的任务,添加新的任务
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(2),new ThreadPoolExecutor.DiscardOldestPolicy());

        try{
    
    
            threadPoolExecutor.execute(new Task(1));
            threadPoolExecutor.execute(new Task(2));
            threadPoolExecutor.execute(new Task(3));
            //丢弃头部的任务需要打开,前三个注释掉即可
            threadPoolExecutor.execute(new Task(4));

        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            //关闭线程
            threadPoolExecutor.shutdown();
        }
    }
}

Résultats de l'exécution
insérez la description de l'image ici
  Nous utilisons maintenant la stratégie de rejet DiscardOldestPolicy pour créer un pool de threads avec une file d'attente de threads et deux files d'attente de tâches. Nous avons ajouté quatre threads au pool de threads. Le résultat de l'exécution est que le deuxième thread n'est pas exécuté et que le quatrième thread Ceci est cohérent avec la politique de rejet DiscardOldestPolicy que nous avons définie, en annulant le thread principal dans la file d'attente des tâches et en ajoutant un nouveau thread à la file d'attente des tâches.
insérez la description de l'image ici

opération de fermeture

  Après la fermeture du pool de threads, le pool de threads ne continuera pas à recevoir de nouvelles tâches et continuera à exécuter les tâches dans la file d'attente des tâches

/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.close
 * @Author: Wuzilong
 * @Description: 关闭线程池,不继续接收新的任务,会继续执行完任务队列中的任务
 * @CreateTime: 2023-07-24 08:54
 * @Version: 1.0
 */

public class Client {
    
    
    public static void main(String[] args) {
    
    
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.AbortPolicy());
        try{
    
    
            threadPoolExecutor.execute(new Task(1));
            //将任务队列中的任务继续执行完毕
            threadPoolExecutor.execute(new Task(3));
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            threadPoolExecutor.shutdown();
            //关闭线程之后继续提交任务看看是否被拒绝
            //threadPoolExecutor.execute(new Task(2));
        }
    }
}

Résultat de l'exécution
1. Le résultat de la poursuite de l'exécution de la tâche
insérez la description de l'image ici
2. Le résultat du rejet de la tâche
insérez la description de l'image ici

opération retardée

exécuter une tâche

code client

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Task task=new Task();
        ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(5);
        System.out.println("提交任务时间"+LocalTime.now());
        scheduledExecutorService.schedule(task,3, TimeUnit.SECONDS);
        scheduledExecutorService.shutdown();
    }
}

code d'entreprise spécifique

public class Task implements Runnable{
    
    
    @Override
    public void run() {
    
    
        System.out.println("执行方法时间"+ LocalTime.now());
    }
}

Exécution des résultats
insérez la description de l'image ici
La soumission d'une tâche exécutable est identique à la soumission d'une tâche appelable. Ajustez simplement l'interface implémentée. Il n'y a pas trop de manifestations ici. Les paramètres de la méthode schedule : le premier est l'objet tâche, le second est l'heure et le troisième est l'unité de temps.

tâches répétitives

Temps fixe

code d'entreprise spécifique

public class Task implements Runnable{
    
    
    @Override
    public void run() {
    
    
        System.out.println("执行方法时间"+ LocalTime.now());
        try{
    
    
            //线程休眠1秒钟,模拟任务执行时长
            Thread.sleep(1000);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }
}

code client

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Task task=new Task();
        ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(5);
        System.out.println("提交任务时间"+LocalTime.now());
        scheduledExecutorService.scheduleAtFixedRate(task,1,1, TimeUnit.SECONDS);
        try{
    
    
            //当前下次线程休眠5秒
            TimeUnit.SECONDS.sleep(5);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }

        scheduledExecutorService.shutdown();
    }
}

Le résultat courant
insérez la description de l'image ici
est exécuté toutes les secondes. Les paramètres passés dans la méthode scheduleAtFixedRate sont les tâches à exécuter, le temps de retard et le temps fixe pour chaque retard. Dans l'exemple, 1 est passé pour indiquer qu'il est exécuté toutes les secondes. Le Un le plus efficace est l'unité de temps.

Intervalles

code client

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Task task=new Task();
        ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(5);
        System.out.println("提交任务时间"+LocalTime.now());
        scheduledExecutorService.scheduleWithFixedDelay(task,2,1, TimeUnit.SECONDS);
        try{
    
    
            //当前下次线程休眠5秒
            TimeUnit.SECONDS.sleep(5);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }

        scheduledExecutorService.shutdown();
    }
}

Le code métier spécifique est cohérent avec le temps fixe et ne sera pas repris ici.
résultat de l'opération
insérez la description de l'image ici

Exécuter une fois toutes les deux secondes en partant du principe que la tâche précédente est exécutée. Les paramètres passés dans la méthode scheduleWithFixedDelay sont la tâche à exécuter, le temps de retard et le temps fixe pour chaque retard. Dans l'exemple, 2 est passé pour indiquer qu'elle est avant En partant du principe qu'une tâche est exécutée, elle est exécutée toutes les deux secondes, et la dernière est l'unité de temps.

insérez la description de l'image ici

Comparaison du temps fixe et du temps d'intervalle

insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/weixin_45490198/article/details/131863455
conseillé
Classement