[Questions d'entrevue Java] Programmation simultanée et points de connaissances liés au pool de threads qui doivent être demandés par les sociétés Internet telles que Ali, JD, ByteDance, etc.

Faites attention, ne vous perdez pas; continuez à mettre à jour les technologies et les informations relatives à Java! ! !
Le contenu provient des contributions des amis du groupe! Merci pour le soutien!

Pourquoi utiliser le pool de threads?

Technologie de mise en commun: Réduisez la consommation de ressources à chaque fois et améliorez l'utilisation des ressources. Le pool de threads fournit un moyen de limiter et de gérer les ressources (y compris l'exécution d'une tâche).
Chaque pool de threads gère également certaines statistiques de base, telles que le nombre de tâches terminées.

Avantages de l'utilisation du pool de threads:

Réduisez la consommation de ressources. Réduisez la consommation causée par la création et la destruction des threads en réutilisant les threads créés.
-Améliorer la vitesse de réponse. Lorsque la tâche arrive, la tâche peut être exécutée immédiatement sans attendre la création du thread.
-Améliorer la gestion des threads. Les threads sont des ressources rares. S'ils sont créés de manière illimitée, ils consommeront non seulement des ressources système, mais réduiront également la stabilité du système. Le pool de threads peut être utilisé pour une allocation, un réglage et une surveillance uniformes.

Le principe de réalisation du pool de threads?

exécuter le code source de la méthode

 public void execute(Runnable command) {
        // 如果任务为null,则抛出异常。        if (command == null)
            throw new NullPointerException();        // ctl 中保存的线程池当前的一些状态信息  AtomicInteger        int c = ctl.get();        //判断当前线程池中执行的任务数量是否小于corePoolSize        if (workerCountOf(c) < corePoolSize) {
            //如果小于,则通过addWorker新建一个线程,然后,启动该线程从而执行任务。            if (addWorker(command, true))
                return;
            c = ctl.get();        }        //通过 isRunning 方法判断线程池状态        //线程池处于 RUNNING 状态才会被并且队列可以加入任务        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();            // 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务。            // 并尝试判断线程是否全部执行完毕。同时执行拒绝策略。            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 如果当前线程池为空就新创建一个线程并执行。            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }        //通过addWorker新建一个线程,并将任务(command)添加到该线程中;
        //然后,启动该线程从而执行任务。        //如果addWorker执行失败,则通过reject()执行相应的拒绝策略的内容。        else if (!addWorker(command, false))
            reject(command);
    }

Lorsque le pool de threads crée un thread, il encapsule le thread dans un thread de travail. Une fois que le worker a terminé la tâche, il obtient cycliquement les tâches de la file d'attente de travail pour exécution.

 final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();        Runnable task = w.firstTask;        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {//循环执行任务
                w.lock();                //如果线程池正在停止,确保线程被中断
                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++;                    w.unlock();                }            }            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);        }    }

Insérez la description de l'image ici

  1. Le pool de threads détermine si les threads du pool de threads principaux [corePoolSize] exécutent tous des tâches. Si ce n'est pas le cas, créez un nouveau thread de travail pour effectuer la tâche. Si les threads du pool de threads principaux exécutent tous des tâches, entrez dans le processus suivant.
  2. Le pool de threads juge si la file d'attente de travail [BlockingQueue] est pleine. Si la file d'attente de travail n'est pas pleine, les tâches nouvellement soumises sont stockées dans cette file d'attente de travail. Si la file d'attente de travail est pleine, entrez dans le processus suivant.
  3. Le pool de threads juge si les threads du pool de threads [maximumPoolSize] fonctionnent tous. Sinon, créez un nouveau thread de travail pour effectuer la tâche. S'il est plein, passez à la stratégie de saturation [RejectedExecutionHandler.rejectedExecution ()] pour gérer cette tâche.

Insérez la description de l'image ici

Comment utiliser le pool de threads?

Analyse importante de ThreadPoolExecutor

Paramètres importants de la méthode de construction

ThreadPoolExecutor方法的构造参数有很多,我们看看最长的那个就可以了:
 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: le nombre de threads principaux définit le nombre minimum de threads pouvant s'exécuter simultanément.
  • maximumPoolSize: lorsque les tâches stockées dans la file d'attente atteignent la capacité de la file d'attente, le nombre de threads qui peuvent actuellement s'exécuter en même temps devient le nombre maximum de threads. [Si vous utilisez une file d'attente illimitée, ce paramètre n'a aucun effet]
  • workQueue: lorsqu'une nouvelle tâche arrive, elle déterminera d'abord si le nombre de threads en cours d'exécution atteint le nombre de threads principaux. Si elle atteint le nombre de threads principaux, la nouvelle tâche sera stockée dans la file d'attente.
  • keepAliveTime: Lorsque le nombre de threads dans le pool de threads est supérieur à corePoolSize, si aucune nouvelle tâche n'est soumise à ce moment, les threads en dehors du thread principal ne seront pas détruits immédiatement, mais attendront que le temps d'attente dépasse -
    keepAliveTime sera recyclé détruire.
  • unit: l'unité de temps de keepAliveTime.
  • threadFactory: utilisé pour définir la fabrique de création de threads. Vous pouvez définir un nom plus significatif pour chaque thread créé via la fabrique de threads.
  • handler: stratégie de saturation Lorsque le nombre de threads en cours d'exécution en même temps atteint le nombre maximum de threads [maximumPoolSize] et que la file d'attente est pleine, la stratégie de saturation sera exécutée.

Utilisation simple du pool de threads

public class ThreadPoolTest {
    private static final int CORE_POOL_SIZE = 5; //核心线程数
    private static final int MAX_POOL_SIZE = 10; //最大线程数
    private static final int QUEUE_CAPACITY = 100; //任务队列的容量
    private static final Long KEEP_ALIVE_TIME = 1L; //等待时间
    public static void main(String[] args) {        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(                CORE_POOL_SIZE,                MAX_POOL_SIZE,                KEEP_ALIVE_TIME,                TimeUnit.SECONDS,                new ArrayBlockingQueue<>(QUEUE_CAPACITY),                new ThreadPoolExecutor.AbortPolicy());        for(int i = 0; i < 10 ; i ++){
            Runnable worker = new MyRunnable(""+ i); //创建任务
            threadPool.execute(worker); //通过execute提交
        }        threadPool.shutdown();        while(!threadPool.isTerminated()){
        }        System.out.println("Finished all threads");
    }}class MyRunnable implements Runnable {    private String command;    MyRunnable(String s) {
        this.command = s;
    }    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
        processCommand();        System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
    }    private void processCommand() {        try {            Thread.sleep(5000);
        } catch (InterruptedException e) {            e.printStackTrace();        }    }    @Override    public String toString() {        return this.command;
    }}

Quelles sont les files d'attente de tâches?

  • ArrayBlockingQueue: Une file d'attente de blocage bornée basée sur la structure du tableau, qui trie les éléments selon le principe FIFO.
  • LinkedBlockingQueue: file d'attente de blocage basée sur la structure de la liste liée, tri des éléments par FIFO, le débit est généralement supérieur à ArrayBlockingQueue, Executors.newFixedThreadPool () utilise cette file d'attente.
  • SynchronousQueue: file d'attente de blocage qui ne stocke pas d'éléments. Chaque opération d'insertion doit attendre qu'un autre thread appelle l'opération de suppression, sinon l'opération d'insertion a été bloquée et le débit est généralement supérieur à LinkedBlockingQueue. Cette file d'attente est utilisée par Executors.newCachedThreadPool () .
  • PriorityBlockingQueue: une file d'attente de blocage infinie avec priorité.

Quelles sont les stratégies de saturation?

  • ThreadPoolExecutor.AbortPolicy: Lancez une RejectedExecutionException pour rejeter le traitement de la nouvelle tâche. [Stratégie de saturation par défaut]
  • ThreadPoolExecutor.CallerRunsPolicy [Fournir une file d'attente évolutive]: Appelez pour exécuter votre propre thread pour exécuter la tâche, c'est-à-dire exécuter la tâche rejetée directement dans le thread qui appelle la méthode d'exécution. Si le programme d'exécution est fermé, la tâche sera abandonnée. Par conséquent, cette stratégie réduira la vitesse de soumission de nouvelles tâches et affectera la performance globale du programme. Si votre application peut tolérer ce délai et que vous avez besoin d'une demande de tâche à exécuter, vous pouvez choisir cette stratégie. public
    void rejetéExecution (Runnable r, ThreadPoolExecutor e) {if
    (! e.isShutdown ()) {r.run ();}}
  • ThreadPoolExecutor.DiscardPolicy: ne traitez pas de nouvelles tâches, jetez-les simplement.
  • ThreadPoolExecutor.DiscardOldestPolicy: cette stratégie rejettera la demande de tâche non traitée la plus ancienne et exécutera la tâche en cours. public void
    rejetéExecution (Runnable r, ThreadPoolExecutor e) {if (! e.isShutdown ()) {e.getQueue (). poll ();
    e.execute®;}}

Bien sûr, vous pouvez également personnaliser la stratégie de rejet en fonction de vos besoins et vous devez implémenter RejectedExecutionHandler.

Comment créer un pool de threads?

1. Utilisez différentes méthodes de construction de ThreadPoolExecutor.

2. Trois types de ThreadPoolExecutor peuvent être créés via Executors, la classe d'outils du framework Executor.

"Alibaba Java Development Manual" ( cliquez ici pour obtenir ce manuel gratuitement ) Le pool de threads forcés n'est pas autorisé à utiliser des exécuteurs pour créer, mais grâce à la méthode ThreadPoolExecutor, cette méthode de traitement rend les étudiants en écriture plus clairs sur le fonctionnement du pool de threads Règles pour éviter le risque d'épuisement des ressources

Les inconvénients des exécuteurs retournant des objets de pool de threads sont les suivants: FixedThreadPool et SingleThreadExecutor:
la longueur de file d'attente autorisée pour les demandes est Integer.MAX_VALUE, qui peut accumuler un grand nombre de demandes, conduisant à MOO. CachedThreadPool et
ScheduledThreadPool: le nombre de threads autorisés à être créés est Integer.MAX_VALUE, ce qui peut créer un grand nombre de threads, conduisant à
MOO.

La différence entre la méthode d'exécution et la méthode d'envoi?

La méthode execute () est utilisée pour soumettre des tâches qui ne nécessitent pas de valeur de retour, il est donc impossible de déterminer si la tâche est exécutée avec succès par le pool de threads;

threadPool.execute(new Runnable() {
    @Override
    public void run() {
    }}); //通过execute提交

La méthode submit () est utilisée pour soumettre des tâches qui nécessitent des valeurs de retour. Le pool de threads renverra un objet de type Future. Cet
objet Future peut être utilisé pour déterminer si la tâche est exécutée avec succès et la valeur renvoyée peut être obtenue via la méthode get () de Future. La méthode get () bloquera le thread actuel jusqu'à ce que la tâche soit terminée, et utilise
get La méthode (long timeout, TimeUnit unit) bloque le thread en cours pendant un certain temps et revient immédiatement. À ce stade, la tâche peut ne pas être terminée.

Future<Object> future = threadPool.submit(hasReturnValueTask);
try{
    Object s = future.get();
}catch(InterruptedException e){
    //处理中断异常
}catch(ExecutionException e){
 //处理无法执行任务异常
}finally{
 threadPool.shutdown();
}

Remarques finales

De plus, j'ai compilé de nombreuses questions d'entrevue de grandes sociétés Internet, ainsi que de nombreux modèles de CV. Les étudiants qui en ont besoin, consultez les commentaires ci-dessous pour les recevoir gratuitement
Insérez la description de l'image ici
Insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/yueyunyin/article/details/108752803
conseillé
Classement