web-dev-qa-db-fra.com

Comment faire en sorte que la méthode submit () de ThreadPoolExecutor soit bloquée si elle est saturée?

Je souhaite créer une variable ThreadPoolExecutor telle que, lorsqu'elle a atteint sa taille maximale et que la file d'attente soit pleine, la méthode submit() se bloque lors de l'ajout de nouvelles tâches. Dois-je implémenter une RejectedExecutionHandler personnalisée à cet effet ou existe-t-il un moyen de le faire à l'aide d'une bibliothèque Java standard?

93
Fixpoint

Une des solutions possibles que je viens de trouver:

public class BoundedExecutor {
    private final Executor exec;
    private final Semaphore semaphore;

    public BoundedExecutor(Executor exec, int bound) {
        this.exec = exec;
        this.semaphore = new Semaphore(bound);
    }

    public void submitTask(final Runnable command)
            throws InterruptedException, RejectedExecutionException {
        semaphore.acquire();
        try {
            exec.execute(new Runnable() {
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            semaphore.release();
            throw e;
        }
    }
}

Y a-t-il d'autres solutions? Je préférerais quelque chose basé sur RejectedExecutionHandler puisqu'il semble être un moyen standard de gérer de telles situations.

44
Fixpoint

Vous pouvez utiliser ThreadPoolExecutor et une blockingQueue:

public class ImageManager {
    BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(blockQueueSize);
    RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
    private ExecutorService executorService =  new ThreadPoolExecutor(numOfThread, numOfThread, 
        0L, TimeUnit.MILLISECONDS, blockingQueue, rejectedExecutionHandler);

    private int downloadThumbnail(String fileListPath){
        executorService.submit(new yourRunnable());
    }
}
27
DiTime4Tea

Vous devez utiliser CallerRunsPolicy, qui exécute la tâche rejetée dans le thread appelant. De cette façon, il ne peut pas soumettre de nouvelles tâches à l'exécuteur jusqu'à ce que cette tâche soit terminée. Il y aura alors des threads de pool libres ou le processus se répètera.

http://Java.Sun.com/j2se/1.5.0/docs/api/Java/util/concurrent/ThreadPoolExecutor.CallerRunsPolicy.html

De la docs:

Tâches rejetées

Les nouvelles tâches soumises dans la méthode execute (Java.lang.Runnable) seront rejeté lorsque l'exécuteur a été arrêté, et aussi quand l'exécuteur utilise des limites finies pour les deux maximum threads et capacité de la file d'attente de travail, et est saturé. Dans les deux cas, le La méthode execute appelle le fichier RejectedExecutionHandler.rejectedExecution (Java.lang.Runnable, Java.util.concurrent.ThreadPoolExecutor) méthode de son RejectedExecutionHandler. Quatre Les politiques de gestionnaire prédéfinies sont à condition de:

  1. Dans le ThreadPoolExecutor.AbortPolicy par défaut, le Handler lance un runtime RejectedExecutionException sur rejet.
  2. Dans ThreadPoolExecutor.CallerRunsPolicy, le thread qui appelle s'exécute lui-même exécute la tâche. Ceci fournit un simple mécanisme de contrôle de rétroaction qui va ralentir le rythme des nouvelles tâches soumis.
  3. Dans ThreadPoolExecutor.DiscardPolicy, un La tâche qui ne peut pas être exécutée est simplement chuté.
  4. Dans ThreadPoolExecutor.DiscardOldestPolicy, si l'exécuteur n'est pas arrêté, le La tâche en tête de la file d'attente est abandonné, puis l'exécution est relancée (qui peut à nouveau échouer, entraînant la répétition de cette opération.)

Assurez-vous également d'utiliser une file d'attente limitée, telle que ArrayBlockingQueue, lors de l'appel du constructeur ThreadPoolExecutor. Sinon, rien ne sera rejeté.

Edit: en réponse à votre commentaire, définissez la taille de ArrayBlockingQueue sur égale à la taille maximale du pool de threads et utilisez AbortPolicy.

Edit 2: Ok, je vois où vous voulez en venir. Qu'en est-il: substituez la méthode beforeExecute() pour vérifier que getActiveCount() ne dépasse pas getMaximumPoolSize() et, si tel est le cas, mettez en veille et réessayez?

12
danben

Consultez quatre alternatives pour ce faire: Création d'un NotifyingBlockingThreadPoolExecutor

12
the_void

Hibernate a une BlockPolicy qui est simple et peut faire ce que vous voulez:

Voir: Executors.Java

/**
 * A handler for rejected tasks that will have the caller block until
 * space is available.
 */
public static class BlockPolicy implements RejectedExecutionHandler {

    /**
     * Creates a <tt>BlockPolicy</tt>.
     */
    public BlockPolicy() { }

    /**
     * Puts the Runnable to the blocking queue, effectively blocking
     * the delegating thread until space is available.
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        try {
            e.getQueue().put( r );
        }
        catch (InterruptedException e1) {
            log.error( "Work discarded, thread was interrupted while waiting for space to schedule: {}", r );
        }
    }
}
5
Nate Murray

La réponse BoundedExecutor citée ci-dessus dans La concurrence de Java en pratique ne fonctionne correctement que si vous utilisez une file d'attente sans limite pour l'exécuteur ou si la limite du sémaphore n'est pas supérieure à la taille de la file. Le sémaphore est un état partagé entre le thread de soumission et les threads du pool, ce qui permet de saturer l'exécuteur même si la taille de la file d'attente <lié <= (taille de la file d'attente + taille du pool).

Utiliser CallerRunsPolicy n’est valide que si vos tâches ne s’exécutent pas indéfiniment. Dans ce cas, votre thread de soumission restera dans rejectedExecution pour toujours, et une mauvaise idée si l’exécution de vos tâches prend du temps, car le thread de soumission ne peut pas soumettre de nouveau. tâches ou faire autre chose si elle exécute une tâche elle-même.

Si ce n'est pas acceptable, je suggère de vérifier la taille de la file d'attente limitée de l'exécuteur avant de soumettre une tâche. Si la file d'attente est pleine, attendez quelques instants avant de tenter de soumettre à nouveau. Le débit en souffrira, mais je suggère qu'il s'agisse d'une solution plus simple que la plupart des solutions proposées et que vous êtes assuré qu'aucune tâche ne sera rejetée.

5
stephan f

La classe suivante enveloppe un ThreadPoolExecutor et utilise un sémaphore pour bloquer puis la file d'attente de travail est pleine:

public final class BlockingExecutor { 

    private final Executor executor;
    private final Semaphore semaphore;

    public BlockingExecutor(int queueSize, int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit, ThreadFactory factory) {
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
        this.executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, queue, factory);
        this.semaphore = new Semaphore(queueSize + maxPoolSize);
    }

    private void execImpl (final Runnable command) throws InterruptedException {
        semaphore.acquire();
        try {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            // will never be thrown with an unbounded buffer (LinkedBlockingQueue)
            semaphore.release();
            throw e;
        }
    }

    public void execute (Runnable command) throws InterruptedException {
        execImpl(command);
    }
}

Cette classe de wrapper est basée sur une solution donnée dans le livre "Java Concurrency in Practice" de Brian Goetz. La solution dans le livre ne prend que deux paramètres de constructeur: une Executor et une borne utilisée pour le sémaphore. Ceci est montré dans la réponse donnée par Fixpoint. Cette approche pose un problème: elle peut se trouver dans un état où les threads de pool sont occupés, la file d’attente est saturée, mais le sémaphore vient de délivrer un permis. (semaphore.release() dans le bloc finally). Dans cet état, une nouvelle tâche peut récupérer le permis qui vient d'être publié, mais est rejetée car la file d'attente des tâches est saturée. Bien sûr, ce n'est pas quelque chose que vous voulez; vous voulez bloquer dans ce cas.

Pour résoudre ce problème, nous devons utiliser une file d'attente non liée , comme le mentionne clairement JCiP. Le sémaphore agit en tant que garde, donnant l’effet d’une taille de file virtuelle. Cela a pour effet secondaire qu'il est possible que l'unité puisse contenir des tâches maxPoolSize + virtualQueueSize + maxPoolSize. Pourquoi donc? A cause de semaphore.release() dans le bloc finally. Si tous les threads du pool appellent cette instruction en même temps, les autorisations maxPoolSize sont libérées, ce qui permet au même nombre de tâches d'entrer dans l'unité. Si nous utilisions une file d'attente limitée, celle-ci serait toujours pleine, ce qui entraînerait le rejet de la tâche. Maintenant, comme nous savons que cela ne se produit que lorsqu'un thread de pool est presque terminé, cela ne pose pas de problème. Nous savons que le thread de pool ne bloquera pas, une tâche sera bientôt prise dans la file d'attente. 

Vous pouvez cependant utiliser une file d'attente délimitée. Assurez-vous simplement que sa taille est égale à virtualQueueSize + maxPoolSize. Les tailles plus grandes sont inutiles, le sémaphore empêchera de laisser plus d'éléments. Les tailles plus petites entraîneront des tâches rejetées. Le risque que des tâches soient rejetées augmente à mesure que la taille diminue. Par exemple, supposons que vous souhaitiez un exécuteur lié avec maxPoolSize = 2 et virtualQueueSize = 5. Ensuite, prenez un sémaphore avec 5 + 2 = 7 permis et une taille de file d'attente réelle de 5 + 2 = 7. Le nombre réel de tâches pouvant être dans l'unité est alors 2 + 5 + 2 = 9. Lorsque l'exécuteur est plein (5 tâches en file d'attente, 2 dans le pool de threads, donc 0 autorisations disponibles) et que TOUS les threads de pool libèrent leurs autorisations, il est alors possible d'obtenir exactement 2 autorisations pour les tâches entrantes.

Maintenant, la solution de JCiP est un peu fastidieuse à utiliser car elle n’impose pas toutes ces contraintes (file d’attente non limitée, ou liée à ces restrictions mathématiques, etc.). Je pense que cela sert uniquement à titre d'exemple pour montrer comment vous pouvez créer de nouvelles classes de thread thread safe basées sur les pièces déjà disponibles, mais pas en tant que classe réutilisable complète. Je ne pense pas que ce soit là l'intention de l'auteur.

3
Timmos

Je sais, c'est un hack, mais à mon avis le hack le plus net entre ceux proposés ici ;-)

Etant donné que ThreadPoolExecutor utilise "l'offre" de la file de blocage, au lieu de "put", permet de remplacer le comportement de "l'offre" de la file de blocage:

class BlockingQueueHack<T> extends ArrayBlockingQueue<T> {

    BlockingQueueHack(int size) {
        super(size);
    }

    public boolean offer(T task) {
        try {
            this.put(task);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return true;
    }
}

ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 2, 1, TimeUnit.MINUTES, new BlockingQueueHack(5));

Je l'ai testée et cela semble fonctionner… .. La mise en œuvre d'une politique de délai d'attente est laissée à l'exercice du lecteur.

3
Jakub

vous pouvez utiliser un RejectedExecutionHandler personnalisé comme celui-ci

ThreadPoolExecutor tp= new ThreadPoolExecutor(core_size, // core size
                max_handlers, // max size 
                timeout_in_seconds, // idle timeout 
                TimeUnit.SECONDS, queue, new RejectedExecutionHandler() {
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        // This will block if the queue is full
                        try {
                            executor.getQueue().put(r);
                        } catch (InterruptedException e) {
                            System.err.println(e.getMessage());
                        }

                    }
                });
2

J'ai trouvé cette politique de rejet dans le client de recherche élastique. Il bloque le fil appelant sur la file d'attente de blocage. Code ci-dessous 

 static class ForceQueuePolicy implements XRejectedExecutionHandler 
 {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) 
        {
            try 
            {
                executor.getQueue().put(r);
            } 
            catch (InterruptedException e) 
            {
                //should never happen since we never wait
                throw new EsRejectedExecutionException(e);
            }
        }

        @Override
        public long rejected() 
        {
            return 0;
        }
}
0
Vladislav

Pour éviter les problèmes avec la solution @FixPoint. Vous pouvez utiliser ListeningExecutorService et publier le sémaphore onSuccess et onFailure dans FutureCallback.

0
vamsu

Vous devriez jeter un coup d'oeil à ce lien (notifying-blocking-thread-pool) qui résume plusieurs solutions et enfin en donner une élégante avec notification.

0
Nereis

J'avais le même besoin par le passé: une sorte de file d'attente de blocage avec une taille fixe pour chaque client sauvegardé par un pool de threads partagé. J'ai fini par écrire mon propre genre de ThreadPoolExecutor:

UserThreadPoolExecutor (file d'attente bloquante (par client) + pool de threads (partagé entre tous les clients))

Voir: https://github.com/d4rxh4wx/UserThreadPoolExecutor

Un nombre maximum de threads à partir d'un ThreadPoolExecutor partagé est attribué à chaque utilisateur UserThreadPoolExecutor.

Chaque UserThreadPoolExecutor peut:

  • soumettre une tâche à l'exécuteur du pool de threads partagé si son quota n'est pas atteint. Si son quota est atteint, le travail est mis en file d'attente (blocage non consommateur en attente de la CPU). Une fois que l'une des tâches soumises est terminée, le quota est décrémenté, ce qui permet de soumettre une autre tâche en attente à ThreadPoolExecutor.
  • attendre que les tâches restantes soient terminées
0
d4rxh4wx

Je n'aime pas toujours CallerRunsPolicy, d'autant plus qu'il permet à la tâche rejetée de "sauter la file d'attente" et de s'exécuter avant les tâches soumises précédemment. De plus, l'exécution de la tâche sur le thread appelant peut prendre beaucoup plus de temps que d'attendre la disponibilité du premier emplacement.

J'ai résolu ce problème en utilisant un RejectedExecutionHandler personnalisé, qui bloque simplement le fil d'appel pendant un moment, puis essaie de soumettre à nouveau la tâche:

public class BlockWhenQueueFull implements RejectedExecutionHandler {

    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

        // The pool is full. Wait, then try again.
        try {
            long waitMs = 250;
            Thread.sleep(waitMs);
        } catch (InterruptedException interruptedException) {}

        executor.execute(r);
    }
}

Cette classe peut simplement être utilisée dans l'exécuteur du pool de threads en tant que RejectedExecutinHandler comme toute autre, par exemple:

executorPool = new ThreadPoolExecutor(1, 1, 10,
                                      TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
                                      new BlockWhenQueueFull());

Le seul inconvénient que je vois, c’est que le fil d’appel peut être verrouillé un peu plus longtemps que ce qui est strictement nécessaire (jusqu’à 250 ms). De plus, étant donné que cet exécuteur est effectivement appelé de manière récursive, de très longues attentes avant qu'un thread devienne disponible (en heures) peuvent entraîner un débordement de pile.

Néanmoins, j'aime personnellement cette méthode. Il est compact, facile à comprendre et fonctionne bien. 

0
TinkerTank

Je crois qu'il existe un moyen assez élégant de résoudre ce problème en utilisant Java.util.concurrent.Semaphore et en déléguant le comportement de Executor.newFixedThreadPool. Le nouveau service exécuteur n'exécutera la nouvelle tâche que lorsqu'un thread le fera. Le blocage est géré par Semaphore avec un nombre de permis égal au nombre de threads. Lorsqu'une tâche est terminée, elle renvoie un permis.

public class FixedThreadBlockingExecutorService extends AbstractExecutorService {

private final ExecutorService executor;
private final Semaphore blockExecution;

public FixedThreadBlockingExecutorService(int nTreads) {
    this.executor = Executors.newFixedThreadPool(nTreads);
    blockExecution = new Semaphore(nTreads);
}

@Override
public void shutdown() {
    executor.shutdown();
}

@Override
public List<Runnable> shutdownNow() {
    return executor.shutdownNow();
}

@Override
public boolean isShutdown() {
    return executor.isShutdown();
}

@Override
public boolean isTerminated() {
    return executor.isTerminated();
}

@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
    return executor.awaitTermination(timeout, unit);
}

@Override
public void execute(Runnable command) {
    blockExecution.acquireUninterruptibly();
    executor.execute(() -> {
        try {
            command.run();
        } finally {
            blockExecution.release();
        }
    });
}
0
Radoslaw Kachel

J'ai récemment eu besoin de réaliser quelque chose de similaire, mais sur une ScheduledExecutorService

Je devais également m'assurer que je traitais le retard transmis par la méthode et que la tâche était soumise pour être exécutée à ce moment-là, comme l'appelant le prévoyait ou échouait, générant ainsi une RejectedExecutionException.

D'autres méthodes de ScheduledThreadPoolExecutor pour exécuter ou soumettre une tâche en interne appellent #schedule qui invoquera toujours les méthodes remplacées. 

import Java.util.concurrent.*;

public class BlockingScheduler extends ScheduledThreadPoolExecutor {
    private final Semaphore maxQueueSize;

    public BlockingScheduler(int corePoolSize,
                             ThreadFactory threadFactory,
                             int maxQueueSize) {
        super(corePoolSize, threadFactory, new AbortPolicy());
        this.maxQueueSize = new Semaphore(maxQueueSize);
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(delay));
        return super.schedule(command, newDelayInMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay,
                                           TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(callable, unit.toMillis(delay));
        return super.schedule(callable, newDelayInMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(initialDelay));
        return super.scheduleAtFixedRate(command, newDelayInMs, unit.toMillis(period), TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long period,
                                                     TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(initialDelay));
        return super.scheduleWithFixedDelay(command, newDelayInMs, unit.toMillis(period), TimeUnit.MILLISECONDS);
    }

    @Override
    protected void afterExecute(Runnable runnable, Throwable t) {
        super.afterExecute(runnable, t);
        try {
            if (t == null && runnable instanceof Future<?>) {
                try {
                    ((Future<?>) runnable).get();
                } catch (CancellationException | ExecutionException e) {
                    t = e;
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt(); // ignore/reset
                }
            }
            if (t != null) {
                System.err.println(t);
            }
        } finally {
            releaseQueueUsage();
        }
    }

    private long beforeSchedule(Runnable runnable, long delay) {
        try {
            return getQueuePermitAndModifiedDelay(delay);
        } catch (InterruptedException e) {
            getRejectedExecutionHandler().rejectedExecution(runnable, this);
            return 0;
        }
    }

    private long beforeSchedule(Callable callable, long delay) {
        try {
            return getQueuePermitAndModifiedDelay(delay);
        } catch (InterruptedException e) {
            getRejectedExecutionHandler().rejectedExecution(new FutureTask(callable), this);
            return 0;
        }
    }

    private long getQueuePermitAndModifiedDelay(long delay) throws InterruptedException {
        final long beforeAcquireTimeStamp = System.currentTimeMillis();
        maxQueueSize.tryAcquire(delay, TimeUnit.MILLISECONDS);
        final long afterAcquireTimeStamp = System.currentTimeMillis();
        return afterAcquireTimeStamp - beforeAcquireTimeStamp;
    }

    private void releaseQueueUsage() {
        maxQueueSize.release();
    }
}

J'ai le code ici, j'apprécierai vos commentaires . https://github.com/AmitabhAwasthi/BlockingScheduler

0
Dev Amitabh

Voici la solution qui semble fonctionner vraiment bien. Il s'appelle NotifyingBlockingThreadPoolExecutor .

Programme de démonstration.

Edit: Il existe un issue avec ce code, la méthode wait () est boguée L'appel de shutdown () + waitTermination () semble fonctionner correctement.

0
Stefan Reich

Récemment, j'ai trouvé cette question ayant le même problème. L'OP ne le dit pas explicitement, mais nous ne souhaitons pas utiliser la variable RejectedExecutionHandler qui exécute une tâche sur le thread de l'émetteur, car elle sous-utilisera les threads de travail si cette tâche est longue.

En lisant toutes les réponses et tous les commentaires, en particulier la solution défectueuse avec le sémaphore ou en utilisant afterExecute, j’ai jeté un œil plus attentif au code de ThreadPoolExecutor pour voir s’il y avait un moyen de sortir. J'ai été surpris de voir qu'il y a plus de 2000 lignes de code (commenté), dont certaines me font sentir étourdi . Compte tenu de l'exigence assez simple que j'ai réellement — un producteur, plusieurs consommateurs, laissez le producteur bloquer quand aucun consommateur ne peut prendre le travail — j'ai décidé de lancer ma propre solution. Ce n'est pas une ExecutorService mais juste une Executor. Et il n’adapte pas le nombre de threads à la charge de travail, mais ne conserve qu’un nombre fixe de threads, ce qui convient également à mes besoins. Voici le code. N'hésitez pas à en parler :-)

package x;

import Java.util.concurrent.BlockingQueue;
import Java.util.concurrent.Executor;
import Java.util.concurrent.RejectedExecutionException;
import Java.util.concurrent.SynchronousQueue;

/**
 * distributes {@code Runnable}s to a fixed number of threads. To keep the
 * code lean, this is not an {@code ExecutorService}. In particular there is
 * only very simple support to shut this executor down.
 */
public class ParallelExecutor implements Executor {
  // other bounded queues work as well and are useful to buffer peak loads
  private final BlockingQueue<Runnable> workQueue =
      new SynchronousQueue<Runnable>();
  private final Thread[] threads;

  /*+**********************************************************************/
  /**
   * creates the requested number of threads and starts them to wait for
   * incoming work
   */
  public ParallelExecutor(int numThreads) {
    this.threads = new Thread[numThreads];
    for(int i=0; i<numThreads; i++) {
      // could reuse the same Runner all over, but keep it simple
      Thread t = new Thread(new Runner());
      this.threads[i] = t;
      t.start();
    }
  }
  /*+**********************************************************************/
  /**
   * returns immediately without waiting for the task to be finished, but may
   * block if all worker threads are busy.
   * 
   * @throws RejectedExecutionException if we got interrupted while waiting
   *         for a free worker
   */
  @Override
  public void execute(Runnable task)  {
    try {
      workQueue.put(task);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RejectedExecutionException("interrupt while waiting for a free "
          + "worker.", e);
    }
  }
  /*+**********************************************************************/
  /**
   * Interrupts all workers and joins them. Tasks susceptible to an interrupt
   * will preempt their work. Blocks until the last thread surrendered.
   */
  public void interruptAndJoinAll() throws InterruptedException {
    for(Thread t : threads) {
      t.interrupt();
    }
    for(Thread t : threads) {
      t.join();
    }
  }
  /*+**********************************************************************/
  private final class Runner implements Runnable {
    @Override
    public void run() {
      while (!Thread.currentThread().isInterrupted()) {
        Runnable task;
        try {
          task = workQueue.take();
        } catch (InterruptedException e) {
          // canonical handling despite exiting right away
          Thread.currentThread().interrupt(); 
          return;
        }
        try {
          task.run();
        } catch (RuntimeException e) {
          // production code to use a logging framework
          e.printStackTrace();
        }
      }
    }
  }
}
0
Harald

Créez votre propre file d'attente de blocage à utiliser par l'exécuteur, avec le comportement de blocage que vous recherchez, tout en restituant toujours la capacité restante disponible (en veillant à ce que l'exécuteur n'essaye pas de créer plus de threads que son pool principal ni de déclencher le gestionnaire de rejet).

Je pense que cela vous permettra d'obtenir le comportement de blocage que vous recherchez. Un gestionnaire de rejet ne sera jamais pris en compte, car cela indique que l'exécuteur ne peut pas exécuter la tâche. Ce que je pouvais imaginer, c’est que vous ayez une forme d’attente «occupée» dans le gestionnaire. Ce n'est pas ce que vous voulez, vous voulez une file d'attente pour l'exécuteur qui bloque l'appelant ...

0
Fried Hoeben