У меня есть ThreadPoolExecutor - corePoolSize = 5, maxPoolSize = 10 queueSize = 10, keepAlive = 1000 секунд . Я выполняю 100 Runnable задачи. Количество фактически выполняемых задач варьируется, и не все они выполняются. RejectionHandler также не сообщает ничего. Я считаю, что мое понимание ThreadPoolExecutor как-то не так. Кто-нибудь может мне помочь? Как я могу выполнить все задачи?

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThreadPoolExecutor {
    public static void main(String[] args) {
     ThreadFactory threadFactory =  Executors.defaultThreadFactory();
    ArrayBlockingQueue<Runnable> arrayBlockingQueue = new ArrayBlockingQueue<Runnable>(10);

    ThreadPoolExecutor threadPoolExecutor =  new ThreadPoolExecutor(5, 10,1000, TimeUnit.SECONDS, arrayBlockingQueue, threadFactory, new RejectedExecutionHandlerImpl());
    MonitorThread monitor = new MonitorThread(threadPoolExecutor, 3);
    Thread monitorThread = new Thread(monitor);
    monitorThread.start();
    for (int i = 0; i < 100; i++) {
        threadPoolExecutor.execute(new DummyRunnableTask(i));
    }
    //threadPoolExecutor.shutdown();
    //monitor.shutDown();
}
}

class DummyRunnableTask implements Runnable {

  private int i;

    public DummyRunnableTask(int i) {
    super();
    this.i = i;
  }

    @Override
    public void run() {
    /*try {
        Thread.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }*/
    System.out.println("Thread Name:=" + Thread.currentThread().getName()+ " is working for id=" + i);
}

}

class RejectedExecutionHandlerImpl implements RejectedExecutionHandler {

    @Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    System.out.println();
}

}

class MonitorThread implements Runnable{
    private ThreadPoolExecutor executor;
    private int seconds;
    private Boolean run = true;
    public MonitorThread(ThreadPoolExecutor executor, int seconds) {
    super();
    this.executor = executor;
    this.seconds = seconds;
  }

    public void shutDown() {
    this.run = false;
  }

    @Override
    public void run() {
    while (run) {
        System.out.println(
                String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s",
                    this.executor.getPoolSize(),
                    this.executor.getCorePoolSize(),
                    this.executor.getActiveCount(),
                    this.executor.getCompletedTaskCount(),
                    this.executor.getTaskCount(),
                    this.executor.isShutdown(),
                    this.executor.isTerminated()));
            try {
                Thread.sleep(seconds*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }

  }

 }

Пример вывода .

...............
Thread Name:=pool-1-thread-7 is working for id=97
Thread Name:=pool-1-thread-2 is working for id=95
Thread Name:=pool-1-thread-10 is working for id=94
Thread Name:=pool-1-thread-4 is working for id=93
[monitor] [0/5] Active: 0, Completed: 0, Task: 1, isShutdown: false, isTerminated: false
[monitor] [10/5] Active: 0, Completed: 88, Task: 88, isShutdown: false, isTerminated: false
[monitor] [10/5] Active: 0, Completed: 88, Task: 88, isShutdown: false, isTerminated: false
6
de_xtr 31 Авг 2017 в 14:40

4 ответа

Лучший ответ

Предположим, что поток 100 не может быть обработан как maxPoolSize = 10 и queueSize = 10, что означает, что вы можете поместить в ваш исполнитель по запросу в худшем случае только 20 потоков. Лучший вариант может быть изменен в зависимости от производительности и сложности работы внутри каждого потока. Попробуйте увеличить размер очереди до 90. Так что наверняка 90 из них будут ждать, а остальные 10 будут работать. Лучшее объяснение вы можете найти здесь ссылку :

Если запрос не может быть поставлен в очередь, создается новый поток, если только он не превысит MaximumPoolSize, и в этом случае задача будет отклонена.

1
Speise 31 Авг 2017 в 12:58

Я ценю ответ, предоставленный @Speise , но только для того, чтобы добавить ясные и ясные детали; Я бы процитировал утверждение, упомянутое в документах оракула:

Если работает меньше потоков corePoolSize, Исполнитель всегда предпочитает добавлять новый поток, а не ставить в очередь.

Если corePoolSize или несколько потоков запущены, Исполнитель всегда предпочитает ставить запрос в очередь, а не добавлять новый поток.

Если запрос не может быть поставлен в очередь, создается новый поток, если только он не превысит MaximumPoolSize, и в этом случае задача будет отклонена.

Документы очень ясны и гласят, что , чтобы избежать отклонения задач, вы должны увеличить свой maxPoolSize. , а не увеличивать размер Queue, потому что это может быть не так (как вы упоминали в комментариях), когда вы заранее знаете, сколько задач будет отправлено (так что вы можете настроить maxPoolSize). Я бы также рекомендовал вам использовать UnboundedQueues (LinkedBlockingQueue) в этом сценарии типа (однако, может быть одно быстрое исправление для этой ситуации, также вы можете увеличить время отправки запросов в очередь).

Увеличение размера очереди также снижает производительность, поэтому почему бы не оставить ее (соответственно создание потоков) для JVM (поскольку ThreadPool выполняет действие в соответствии с maxPoolSize)

3
Community 20 Июн 2020 в 09:12

Параметры Test ThreadPoolExecutor:

corePoolSize - количество потоков в пуле, даже если они простаивают.

maximumPoolSize - максимальное количество потоков, разрешенных в пуле.

keepAliveTime - когда число потоков больше, чем ядро, это максимальное время, в течение которого избыточные незанятые потоки будут ожидать новых задач перед завершением.

unit - единица времени для аргумента keepAliveTime.

workQueue - очередь для хранения задач перед их выполнением. Эта очередь будет содержать только выполняемые задачи, отправленные методом execute.

обработчик - обработчик, который используется, когда выполнение заблокировано, поскольку достигнуты границы потоков и емкости очереди.

Ваш сценарий: corePoolSize = 5, максимумPoolSize = 10, рабочая очередь = 10

И вы выдвигаете 100 задач для цикла.

Прежде всего, вы никогда не сможете получить более 10 рабочих потоков.

Предположим, что все ваши рабочие потоки заняты, тогда максимальная рабочая очередь может быть равна 10. После этого любая отправка задачи будет отклонена, и вы можете перехватить это отклонение в обработчике отклонения. Так что увеличение рабочей очереди поможет в решении вопроса. (Но не рекомендуется)

То, что вы можете попробовать:

Используйте некоторый промежуток времени в продвижении своей работы вместо использования цикла for, который фактически пытается продвинуть работу в одном и том же экземпляре.

Установите ваш MaximumPoolSize в число ядер X 2 (или 4).

Когда ваша работа отклонена, вы можете попытаться повторно отправить работу после некоторой задержки

PS: это проблема производителя-потребителя. Это всегда требует, чтобы потребитель был быстрее, чем производитель. В какой-то момент продюсер становится быстрее, и мы видим всплеск рабочей нагрузки. Для обработки этого скачка нагрузки разрабатываются очереди, но даже когда очередь переполняется, отклонение отправляется обратно производителю, и обязанность производителя обрабатывать это изящно, либо повторной передачей, либо отбрасыванием.

1
Ritesh 31 Авг 2017 в 14:04

Благодаря @ Mandy8055, @Ritesh, @Speise

Я нашел два способа, которыми эта проблема может быть обработана изящно, если queueSize не может быть предсказано.

  1. Напишите обработчик отклонения, который отправит задачу обратно в очередь. Но с другой стороны, как отметил @Ritesh, производитель может быть быстрее потребителя, и мы можем получить StackOverFlow. Во всяком случае, вот что я сделал:

    class RejectedExecutionHandlerImpl implements RejectedExecutionHandler {
    
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    DummyRunnableTask task =  (DummyRunnableTask)r;
    
    System.out.println("Task id "+task.getI()+" got rejected, resubmitting");
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    executor.execute(r);
    }
    }
    

    Теперь, это не совсем хороший способ справиться с этим, так как вы просто не знаете, займет ли выполнение задачи 100 мс или нет. Может быть, вместо этого мы можем создать список отклоненных задач и снова отправить их обратно.

  2. Другой способ - заблокировать метод submit / execute ThreadPoolExecutor, как только будет достигнут определенный лимит (maxPoolSize + queueSize), и заставить его ждать, пока освободится место. Мы делаем это с помощью ограниченного семафора в пользовательской реализации ThreadPoolExecutor

    class CustomExecutor { 
    
    private final ThreadPoolExecutor executor;
    private final Semaphore semaphore;
    
    public CustomExecutor(int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit,int queueSize, ThreadFactory factory) {
    BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(queueSize);
    this.executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, queue, factory);
    this.semaphore = new Semaphore(queueSize + maxPoolSize);
    }
    public void shutDown(){
    this.executor.shutdown();
    }
    
    public ThreadPoolExecutor getExecutor() {
    return executor;
    }
    
    
    private void exec(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) {
        semaphore.release();
        throw e;
    }
    }
    
    public void execute (Runnable command) throws InterruptedException {
    exec(command);
      }
    }
    

Дайте нам знать, если вы можете думать о любом другом пути.

1
de_xtr 31 Авг 2017 в 15:34