Explication détaillée de la classe d'outils Executors en Java

Exécuteurs

Executors est une classe utilitaire en Java. Fournit des méthodes de fabrique pour créer différents types de pools de threads.

![][2]

Il ressort également de la figure ci-dessus que la méthode Executors de création d'un pool de threads et le pool de threads créé implémentent tous l'interface ExecutorService. Les méthodes couramment utilisées sont les suivantes :

newFiexedThreadPool(int Threads): Créez un pool de threads avec un nombre fixe de threads.

newCachedThreadPool(): crée un pool de threads pouvant être mis en cache, l'appel à execute réutilisera les threads précédemment construits (si des threads sont disponibles). Si aucun thread n'est disponible, un nouveau thread est créé et ajouté au pool. Termine et supprime les threads qui n'ont pas été utilisés pendant 60 secondes du cache.

newSingleThreadExecutor()Créez un exécuteur monothread.

newScheduledThreadPool(int corePoolSize)Créez un pool de threads qui prend en charge le minutage et l'exécution périodique des tâches, qui peut être utilisé pour remplacer la classe Timer dans la plupart des cas.

La classe semble être relativement puissante, et elle utilise le modèle d'usine et a une évolutivité relativement forte. L'important est qu'elle soit plus pratique à utiliser, comme :

ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;

Un pool de threads de taille fixe peut être créé.

Mais pourquoi est-ce que je dis qu'il n'est pas recommandé d'utiliser cette classe pour créer un pool de threads ?

Ce que j'ai mentionné n'est "pas recommandé", mais c'est également clairement indiqué dans le manuel de développement Java d'Alibaba, et le mot utilisé est "non autorisé" à utiliser des exécuteurs pour créer des pools de threads.

Quel est le problème avec les exécuteurs

Il est mentionné dans le Alibaba Java Development Manual que l'utilisation d'exécuteurs pour créer un pool de threads peut provoquer un OOM (OutOfMemory, débordement de mémoire), mais cela n'explique pas pourquoi, alors examinons pourquoi les exécuteurs ne sont pas autorisés à être utilisés ?

Commençons par un exemple simple pour simuler la situation où l'utilisation d'Executors conduit à OOM.

/**
 * @author Hollis
 */
public class ExecutorsDemo {
    private static ExecutorService executor = Executors.newFixedThreadPool(15);
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(new SubThread());
        }
    }
}


class SubThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            //do nothing
        }
    }
}

En spécifiant les paramètres JVM : -Xmx8m -Xms8ml'exécution du code ci-dessus lancera OOM :

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

Le code ci-dessus indique ExecutorsDemo.javaque la ligne 16 de se trouve dans le code executor.execute(new SubThread());.

Pourquoi les exécuteurs sont imparfaits

Grâce à l'exemple ci-dessus, nous savons que Executorsle pool de threads créé présente un risque de MOO, alors quelle en est la raison ? Nous avons besoin d'un code source approfondi Executorspour l'analyser.

En fait, dans le message d'erreur ci-dessus, nous pouvons voir les indices. En fait, il a été dit dans le code ci-dessus que la véritable cause de OOM est en fait la méthode LinkedBlockingQueue.offer.

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

Si les lecteurs regardent le code, ils peuvent également constater que la couche inférieure est en effet LinkedBlockingQueueimplémentée via :

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

Si le lecteur sait quelque chose sur le blocage des files d'attente en Java, vous pourrez peut-être comprendre la raison ici.

Il existe principalement deux implémentations en Java BlockingQueue, à savoir ArrayBlockingQueueet LinkedBlockingQueue.

ArrayBlockingQueueIl s'agit d'une file d'attente de blocage limitée implémentée avec un tableau, et la capacité doit être définie.

LinkedBlockingQueueIl s'agit d'une file d'attente de blocage limitée implémentée avec une liste chaînée. La capacité peut être définie en option. Si elle n'est pas définie, il s'agira d'une file d'attente de blocage illimitée d'une longueur maximale de Integer.MAX_VALUE.

Le problème ici est : si ** n'est pas défini, ce sera une file d'attente de blocage illimitée avec une longueur maximale de Integer.MAX_VALUE. ** Autrement dit, si nous ne définissons pas LinkedBlockingQueuela capacité, sa capacité par défaut sera Integer.MAX_VALUE.

Lors de newFixedThreadPoolsa création LinkedBlockingQueue, aucune capacité n'est spécifiée. À ce stade, LinkedBlockingQueueil s'agit d'une file d'attente illimitée. Pour une file d'attente illimitée, des tâches peuvent être continuellement ajoutées à la file d'attente. Dans ce cas, il peut y avoir des problèmes de dépassement de mémoire en raison d'un trop grand nombre de tâches.

Les problèmes mentionnés ci-dessus se reflètent principalement dans les newFixedThreadPooldeux newSingleThreadExecutorméthodes d'usine. Cela ne signifie pas newCachedThreadPoolque newScheduledThreadPoolces deux méthodes sont sûres. Le nombre maximum de threads créés par ces deux méthodes peut être Integer.MAX_VALUE, et créer autant de threads conduira inévitablement à un MOO.

La bonne posture pour créer un pool de threads

Évitez d'utiliser des exécuteurs pour créer un pool de threads, principalement pour éviter d'utiliser l'implémentation par défaut, nous pouvons alors appeler directement le ThreadPoolExecutorconstructeur pour créer nous-mêmes le pool de threads. Au même moment de la création, BlockQueueil suffit de préciser la capacité.

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));

Dans ce cas, une fois que le nombre de threads soumis dépasse le nombre actuel de threads disponibles, il sera lancé java.util.concurrent.RejectedExecutionException. En effet, la file d'attente utilisée par le pool de threads actuel est une file d'attente limitée et la file d'attente ne peut pas continuer à traiter de nouvelles demandes lorsqu'elle est rempli. Mais les exceptions (Exception) valent mieux que les erreurs (Error).

En plus de leur propre définition ThreadPoolExecutor. Il existe d'autres moyens. À l'heure actuelle, la première fois que vous devriez penser aux bibliothèques open source, telles que apache et guava.

L'auteur recommande d'utiliser ThreadFactoryBuilder fourni par guava pour créer un pool de threads.

public class ExecutorsDemo {


    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();
    
    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
    
    public static void main(String[] args) {
    
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            pool.execute(new SubThread());
        }
    }

}

Lors de la création d'un fil de la manière ci-dessus, non seulement le problème du MOO peut être évité, mais le nom du fil peut également être personnalisé, ce qui facilite la recherche de la source des erreurs.

En pensant à la question, l'auteur de l'article a déclaré: Il vaut mieux avoir une exception (Exception) qu'une erreur (Error), pourquoi dites-vous cela?

Je suppose que tu aimes

Origine blog.csdn.net/zy_dreamer/article/details/132350963
conseillé
Classement