ThreadPoolExecutor avec corePoolSize 0 ne doit pas exécuter de tâches jusqu'à ce que la file d'attente soit complète

je passais par Java Competiency In Practice et je me suis retrouvé coincé au sujet 8.3.1 création de fil et démontage . La note de bas de page suivante met en garde contre le maintien de corePoolSize à zéro.

les développeurs sont parfois tentés de mettre la taille du noyau à zéro de sorte que les threads de l'ouvrier il n'est donc pas exclu que la JVM puisse être démantelée, mais cela peut entraîner un certain nombre de problèmes. comportement étrange - semblant dans les piscines de fil qui n'utilisent pas une queue synchrone pour leur file d'attente de travail (comme newCachedThreadPool). si le pool est déjà à la taille de base, ThreadPoolExecutor crée un nouveau thread seulement si la file d'attente de travail est pleine. Les tâches soumises à un pool de threads avec une file d'attente de travail qui a n'importe quelle capacité et une taille de noyau de zéro n'exécutera pas jusqu'à ce que la queue remplit , qui est habituellement pas ce qui est souhaité.

Afin de vérifier ce que j'ai écrit ce programme qui ne fonctionne pas comme indiqué ci-dessus.

    final int corePoolSize = 0;
    ThreadPoolExecutor tp = new ThreadPoolExecutor(corePoolSize, 1, 5, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>());

    // If the pool is already at the core size
    if (tp.getPoolSize() == corePoolSize) {
        ExecutorService ex = tp;

        // So tasks submitted to a thread pool with a work queue that has any capacity
        // and a core size of zero will not execute until the queue fills up.
        // So, this should not execute until queue fills up.
        ex.execute(() -> System.out.println("Hello"));
    }

Sortie : Bonjour

ainsi, le comportement du programme suggère-t-il que ThreadPoolExecutor crée au moins un thread si une tâche est soumise indépendamment de corePoolSize=0 . Si oui, sur quoi porte l'avertissement dans le manuel?

EDIT: testé le code en jdk1.5.0_22 sur @S. K. proposition du changement suivant:

ThreadPoolExecutor tp = new ThreadPoolExecutor(corePoolSize, 1, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(1));//Queue size is set to 1.

mais avec ce changement, le programme se termine sans impression de sortie.

alors est-ce que j'interprète mal ces affirmations du livre?

EDIT (@sjlee): il est difficile d'ajouter du code dans le commentaire, donc je vais l'ajouter comme une édition ici... Pouvez-vous essayer ce la modification et l'exécution contre les derniers JDK et JDK 1.5?

final int corePoolSize = 0;
ThreadPoolExecutor tp = new ThreadPoolExecutor(corePoolSize, 1, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

// If the pool is already at the core size
if (tp.getPoolSize() == corePoolSize) {
    ExecutorService ex = tp;

    // So tasks submitted to a thread pool with a work queue that has any capacity
    // and a core size of zero will not execute until the queue fills up.
    // So, this should not execute until queue fills up.
    ex.execute(() -> System.out.println("Hello"));
}
tp.shutdown();
if (tp.awaitTermination(1, TimeUnit.SECONDS)) {
    System.out.println("thread pool shut down. exiting.");
} else {
    System.out.println("shutdown timed out. exiting.");
}
5
demandé sur Steve 2018-09-10 10:42:07

2 réponses

en exécutant ce programme dans jdk 1.5,1.6,1.7 et 1.8, j'ai trouvé différentes implémentations de ThreadPoolExecutor#execute(Runnable) dans 1.5,1.6 et 1.7+. Voici ce que j'ai trouvé:

JDK 1.5 mise en œuvre

 //Here poolSize is the number of core threads running.

 public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    for (;;) {
        if (runState != RUNNING) {
            reject(command);
            return;
        }
        if (poolSize < corePoolSize && addIfUnderCorePoolSize(command))
            return;
        if (workQueue.offer(command))
            return;
        Runnable r = addIfUnderMaximumPoolSize(command);
        if (r == command)
            return;
        if (r == null) {
            reject(command);
            return;
        }
        // else retry
    }
}

cette implémentation ne crée pas de thread lorsque corePoolSize est 0, donc la tâche fournie ne s'exécute pas.

JDK 1.6 implementation

//Here poolSize is the number of core threads running.

  public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
}

JDK 1.6 crée un nouveau fil même si le corePoolSize est 0.

JDK 1.7+ implementation (similaire à JDK 1.6 mais avec de meilleures serrures et contrôles d'état)

    public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    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);
}

JDK 1.7 crée aussi un nouveau fil même si le corePoolSize est 0.

ainsi, il semble que corePoolSize=0 soit un cas particulier dans chaque version de JDK 1.5 et JDK 1.6+.

mais il est étrange que l'explication du livre ne correspond à aucun des résultats du programme.

1
répondu Steve 2018-09-19 10:33:05

ressemble à un bug avec des versions java plus anciennes mais il n'existe pas maintenant en Java 1.8.

selon la documentation Java 1.8 de ThreadPoolExecutor.execute() :

     /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     * ....
     */

dans le deuxième point, il y a une revérification après avoir ajouté un ouvrier à la file d'attente que si au lieu de faire la file d'attente de la tâche, un nouveau thread peut être lancé, que le rollback l'enquête et démarrer un nouveau thread.

C'est ce qui se passe. Lors du premier contrôle la tâche est mise en file d'attente, mais pendant la revérification, un nouveau thread est démarré qui exécute votre tâche.

0
répondu S.K. 2018-09-10 08:19:17