Розмір основного пулу проти максимального розміру пулу в ThreadPoolExecutor


105

Яка саме різниця між розміром основного пулу та максимальним розміром пулу, коли ми говоримо з точки зору ThreadPoolExecutor?
Чи можна це пояснити за допомогою прикладу?



Відповіді:


130

З цієї публікації в блозі :

Візьмемо цей приклад. Початковий розмір пулу потоків - 1, розмір основного пулу - 5, максимальний розмір пулу - 10, а черга - 100.

Коли надходять запити, потоків буде створюватися до 5, а потім завдання будуть додаватися до черги, поки вона не досягне 100. Коли черга заповнена, нові потоки будуть створені до maxPoolSize. Як тільки всі потоки будуть використані, і черга заповнена, завдання будуть відхилені. Зі зменшенням черги зменшується і кількість активних потоків.


Це правильно? Я думав, що нові теми будуть створені, поки не досягне maxPoolSize. Тоді будь-які нові потоки будуть поміщені в чергу. Будь ласка, виправте мене, якщо я помиляюся ..
Glide

4
Так, це правильно. Потоки додаватимуться за межі corePoolSize лише за наявності завдань у черзі. Ці додаткові нитки будуть «відмирати» після того, як черга досягне нуля.
Лука

3
Існує цікавий метод, allowCoreThreadTimeOut(boolean)який дозволяє вбити потоки стрижня після заданого часу простою. Якщо встановити для цього значення true і setting core threads= max threadsдозволяє пулу потоків масштабуватись від 0 до max threads.
Ярослав Павлак,

4
Ви просто скопіювали його звідси bigsoft.co.uk/blog/index.php/2009/11/27/…
Кумар Маніш

1
Що відбувається з відхиленими завданнями?
Віск

54

ЯКЩО запущені потоки> corePoolSize & <maxPoolSize , потім створіть нову нитку, якщо загальна черга завдань заповнена та надходить нова.

Документ форми: (якщо запущено більше ніж corePoolSize, але менше, ніж maximumPoolSize , новий потік буде створений, лише якщо черга заповнена.)

Тепер візьмемо простий приклад,

ThreadPoolExecutor executorPool = new ThreadPoolExecutor(5, 10, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));

Тут 5 - corePoolSize - означає, що Jvm створить новий потік для нового завдання для перших 5 завдань. а інші завдання будуть додаватися до черги, поки черга не заповниться (50 завдань).

10 - maxPoolSize - JVM може створити максимум 10 потоків. Означає, якщо запущено вже 5 завдань / потік, а черга заповнена 50 завданнями, що очікують на розгляд, і якщо ще один новий запит / завдання надходить у чергу, тоді JVM створить новий потік до 10 (загальна кількість потоків = попередні 5 + нові 5) ;

new ArrayBlockingQueue (50) = загальний розмір черги - він може встановити в неї 50 завдань.

як тільки всі 10 потоків запущені, і якщо надходить нове завдання, це нове завдання буде відхилено.

Правила внутрішнього створення потоків від SUN:

  1. Якщо кількість потоків менше, ніж corePoolSize, створіть нову тему для запуску нового завдання.

  2. Якщо кількість потоків дорівнює (або перевищує) corePoolSize, поставте завдання в чергу.

  3. Якщо черга заповнена, а кількість потоків менше maxPoolSize, створіть новий потік для виконання завдань.

  4. Якщо черга заповнена, а кількість потоків більше або дорівнює maxPoolSize, відхиліть завдання.

Сподіваюся, це HelpFul .. і, будь ласка, виправте мене, якщо я помиляюся ...


21

Від док .

Коли нове завдання подається у виконанні методу (java.lang.Runnable), і працює менше потоків corePoolSize, створюється новий потік для обробки запиту, навіть якщо інші робочі потоки простоюють. Якщо запущено більше, ніж corePoolSize, але менше максимального потокуPoolSize, новий потік буде створений, лише якщо черга заповнена.

Крім того:

Встановивши corePoolSize і maximumPoolSize однаково, ви створюєте пул потоків фіксованого розміру. Установивши maxPoolSize по суті необмежене значення, таке як Integer.MAX_VALUE, ви дозволяєте пулу вміщувати довільну кількість одночасних завдань. Найчастіше розмір ядра та максимальний розмір пулу встановлюється лише під час побудови, але їх також можна динамічно змінювати за допомогою setCorePoolSize (int) та setMaximumPoolSize (int).


1) Коли нове завдання подається у виконанні методу (java.lang.Runnable) і працює менше потоків corePoolSize, створюється новий потік для обробки запиту, навіть якщо інші робочі потоки простоюють. Чому існує потреба створити новий потік для обробки запиту, якщо є непрацюючі потоки?
user2568266

1
2) Якщо запущено більше ніж corePoolSize, але менше ніж maxPoolSize, новий потік буде створений, лише якщо черга заповнена. Я не розумію різниці між corePoolSize і максимальнимPoolSize тут. По-друге, як може бути заповнена черга, коли потоки менше максимальногоPoolSize? Черга може бути повною, лише якщо потоки дорівнюють максимальномуPoolSize. Чи не так?
користувач2568266

9

Якщо ви вирішили створити ThreadPoolExecutorвручну замість використання Executorsзаводського класу, вам потрібно буде створити та налаштувати його за допомогою одного з його конструкторів. Найбільш обширний конструктор цього класу:

public ThreadPoolExecutor(
    int corePoolSize,
    int maxPoolSize,
    long keepAlive,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    RejectedExecutionHandler handler
);

Як бачите, ви можете налаштувати:

  • Розмір пулу ядра (розмір пулу потоку намагатиметься дотримуватися).
  • Максимальний розмір басейну.
  • Час збереження життя, тобто час, після якого непрацююча нитка може бути зірвана.
  • Черга роботи для утримання завдань, що очікують на виконання.
  • Політика, яка застосовується, коли подання завдання відхилено.

Обмеження кількості завдань, що стоять у черзі

Обмеження кількості паралельних завдань, що виконуються, розмір пулу потоків представляє величезну вигоду для вашої програми та її середовища виконання з точки зору передбачуваності та стабільності: необмежене створення потоку в кінцевому підсумку вичерпає ресурси середовища виконання, і як наслідок це може спричинити ваша програма , серйозні проблеми з продуктивністю, які можуть призвести навіть до нестабільності програми.

Це вирішення лише однієї частини проблеми: ви обмежуєте кількість виконуваних завдань, але не обмежуєте кількість завдань, які можна подати та поставити в чергу для подальшого виконання. У додатку пізніше виникне дефіцит ресурсів, але він з часом випробує його, якщо рівень подання послідовно перевищує швидкість виконання.

Рішення цієї проблеми полягає в: наданні виконавцю черги блокування для утримання завдань, що очікують. У разі заповнення черги надіслане завдання буде "відхилено". TheRejectedExecutionHandlerВикликається , коли уявлення завдання відкидається, і саме тому дієслово відкидається був цитованих в попередньому пункті. Ви можете реалізувати власну політику відхилення або скористатися однією із вбудованих політик, передбачених фреймворком.

За замовчуванням у політиці відхилення виконавець кидає a RejectedExecutionException . Однак інші вбудовані політики дозволяють вам:

  • Відмовтеся від роботи мовчки.
  • Відкиньте найстаріше завдання і спробуйте подати останнє.
  • Виконайте відхилене завдання в потоці абонента.

5

Джерело

Правила розміру пулу ThreadPoolExecutor

Правила розміру a ThreadPoolExecutor's пулу, як правило, неправильно зрозумілі, оскільки він працює не так, як ви вважаєте, що потрібно, або так, як ви хочете.

Візьмемо цей приклад. Початковий розмір пулу потоків - 1, розмір основного пулу - 5, максимальний розмір пулу - 10, а черга - 100.

Шлях Сонця: оскільки запити надходять у потоках, їх буде створено до 5, тоді завдання будуть додаватися до черги, поки вона не досягне 100. Коли черга заповнена, нові потоки будуть створюватися до maxPoolSize. Як тільки всі потоки будуть використані, а черга заповнена, завдання буде відхилено. Зі зменшенням черги зменшується і кількість активних потоків.

Спосіб, передбачений користувачем: оскільки запити, що надходять у потоках, будуть створюватися до 10, тоді завдання будуть додаватися до черги, поки не досягне 100, і тоді вони будуть відхилені. Кількість потоків буде перейменовано на максимум, поки черга не порожня. Коли черга порожня, потоки відмирають, доки не corePoolSizeзалишиться.

Різниця полягає в тому, що користувачі хочуть почати збільшувати розмір пулу раніше і хочуть, щоб черга була меншою, де як метод Sun хоче зберегти розмір пулу невеликим і збільшити його лише після того, як навантаження стане значним.

Ось правила Sun для створення ниток простими словами:

  1. Якщо кількість потоків менше, ніж кількість corePoolSize, створіть нову тему для запуску нового завдання.
  2. Якщо кількість ниток дорівнює (або більше) corePoolSize , поставте завдання в чергу.
  3. Якщо черга заповнена, а кількість потоків менше, ніж maxPoolSize , створіть новий потік для запуску завдань.
  4. Якщо черга заповнена, а кількість потоків більше або дорівнює maxPoolSize, відхиліть завдання. Довгий і короткий з них полягає в тому, що нові потоки створюються лише тоді, коли черга заповнюється, тому, якщо ви використовуєте необмежену чергу, кількість потоків не перевищуватиме corePoolSize.

Для більш повного пояснення дістаньте це з вуст коней: ThreadPoolExecutorдокументація API.

Існує дуже хороша публікація на форумі, яка розповідає вам про те, як це ThreadPoolExecutorпрацює з прикладами коду: http://forums.sun.com/thread.jspa?threadID=5401400&tstart=0

Докладніше: http://forums.sun.com/thread.jspa?threadID=5224557&tstart=450


3

Ви можете знайти визначення термінів corepoolsize та maxpoolsize в javadoc. http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html

У наведеному вище посиланні є відповідь на ваше запитання. Однак лише для того, щоб це було зрозуміло. Додаток буде продовжувати створювати потоки, поки не дійде до corePoolSize. Я думаю, що ідея тут полягає в тому, що цих багатьох потоків має бути достатньо для обробки припливу завдань. Якщо нове завдання з’являється після створення потоків corePoolSize, завдання потрапляють у чергу. Як тільки черга заповниться, виконавець почне створювати нові потоки. Це свого роду балансування. По суті, це означає, що приплив завдань перевищує обробну потужність. Отже, Executor знову почне створювати нові потоки, поки не досягне максимальної кількості потоків. Знову ж таки, нові потоки будуть створені тоді і тільки тоді, коли черга заповнена.


3

Хороше пояснення в цьому блозі:

Ілюстрація

public class ThreadPoolExecutorExample {

    public static void main (String[] args) {
        createAndRunPoolForQueue(new ArrayBlockingQueue<Runnable>(3), "Bounded");
        createAndRunPoolForQueue(new LinkedBlockingDeque<>(), "Unbounded");
        createAndRunPoolForQueue(new SynchronousQueue<Runnable>(), "Direct hand-off");
    }

    private static void createAndRunPoolForQueue (BlockingQueue<Runnable> queue,
                                                                      String msg) {
        System.out.println("---- " + msg + " queue instance = " +
                                                  queue.getClass()+ " -------------");

        ThreadPoolExecutor e = new ThreadPoolExecutor(2, 5, Long.MAX_VALUE,
                                 TimeUnit.NANOSECONDS, queue);

        for (int i = 0; i < 10; i++) {
            try {
                e.execute(new Task());
            } catch (RejectedExecutionException ex) {
                System.out.println("Task rejected = " + (i + 1));
            }
            printStatus(i + 1, e);
        }

        e.shutdownNow();

        System.out.println("--------------------\n");
    }

    private static void printStatus (int taskSubmitted, ThreadPoolExecutor e) {
        StringBuilder s = new StringBuilder();
        s.append("poolSize = ")
         .append(e.getPoolSize())
         .append(", corePoolSize = ")
         .append(e.getCorePoolSize())
         .append(", queueSize = ")
         .append(e.getQueue()
                  .size())
         .append(", queueRemainingCapacity = ")
         .append(e.getQueue()
                  .remainingCapacity())
         .append(", maximumPoolSize = ")
         .append(e.getMaximumPoolSize())
         .append(", totalTasksSubmitted = ")
         .append(taskSubmitted);

        System.out.println(s.toString());
    }

    private static class Task implements Runnable {

        @Override
        public void run () {
            while (true) {
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }
}

Вихід:

---- Bounded queue instance = class java.util.concurrent.ArrayBlockingQueue -------------
poolSize = 1, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 3, maximumPoolSize = 5, totalTasksSubmitted = 1
poolSize = 2, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 3, maximumPoolSize = 5, totalTasksSubmitted = 2
poolSize = 2, corePoolSize = 2, queueSize = 1, queueRemainingCapacity = 2, maximumPoolSize = 5, totalTasksSubmitted = 3
poolSize = 2, corePoolSize = 2, queueSize = 2, queueCapacity = 1, maximumPoolSize = 5, totalTasksSubmitted = 4
poolSize = 2, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 5
poolSize = 3, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 6
poolSize = 4, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 7
poolSize = 5, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 8
Task rejected = 9
poolSize = 5, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 9
Task rejected = 10
poolSize = 5, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 10
--------------------

---- Unbounded queue instance = class java.util.concurrent.LinkedBlockingDeque -------------
poolSize = 1, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 2147483647, maximumPoolSize = 5, totalTasksSubmitted = 1
poolSize = 2, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 2147483647, maximumPoolSize = 5, totalTasksSubmitted = 2
poolSize = 2, corePoolSize = 2, queueSize = 1, queueRemainingCapacity = 2147483646, maximumPoolSize = 5, totalTasksSubmitted = 3
poolSize = 2, corePoolSize = 2, queueSize = 2, queueRemainingCapacity = 2147483645, maximumPoolSize = 5, totalTasksSubmitted = 4
poolSize = 2, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 2147483644, maximumPoolSize = 5, totalTasksSubmitted = 5
poolSize = 2, corePoolSize = 2, queueSize = 4, queueRemainingCapacity = 2147483643, maximumPoolSize = 5, totalTasksSubmitted = 6
poolSize = 2, corePoolSize = 2, queueSize = 5, queueRemainingCapacity = 2147483642, maximumPoolSize = 5, totalTasksSubmitted = 7
poolSize = 2, corePoolSize = 2, queueSize = 6, queueRemainingCapacity = 2147483641, maximumPoolSize = 5, totalTasksSubmitted = 8
poolSize = 2, corePoolSize = 2, queueSize = 7, queueRemainingCapacity = 2147483640, maximumPoolSize = 5, totalTasksSubmitted = 9
poolSize = 2, corePoolSize = 2, queueSize = 8, queueRemainingCapacity = 2147483639, maximumPoolSize = 5, totalTasksSubmitted = 10
--------------------

---- Direct hand-off queue instance = class java.util.concurrent.SynchronousQueue -------------
poolSize = 1, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 1
poolSize = 2, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 2
poolSize = 3, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 3
poolSize = 4, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 4
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 5
Task rejected = 6
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 6
Task rejected = 7
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 7
Task rejected = 8
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 8
Task rejected = 9
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 9
Task rejected = 10
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 10
--------------------


Process finished with exit code 0

1

З книги Основи забезпечення безпеки Java :

CorePoolSize : ThreadPoolExecutor має атрибут corePoolSize, який визначає, скільки потоків він почнеться, поки нові потоки не будуть запущені лише тоді, коли черга заповнена

MaximumPoolSize : Цей атрибут визначає, скільки потоків було запущено максимум. Ви можете встановити це ціле число. MAX_VALUE, щоб не було верхньої межі


0

java.util.concurrent.ThreadPoolExecutor

  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);
    }

0

Розуміння внутрішньої поведінки, ThreadPoolExecutorколи подається нове завдання, допомогло мені зрозуміти, як corePoolSizeі maximumPoolSizeвідрізнятися.

Дозволяти:

  • Nбути кількість потоків в пулі, getPoolSize(). Активні потоки + простої потоків.
  • T бути обсягом завдань, що подаються виконавцю / пулу.
  • Cбути основний розмір пулу, getCorePoolSize(). Скільки потоків можна створити щонайбільше в пулі для вхідних завдань перед тим, як нові завдання потраплять до черги .
  • Mбути максимальний розмір пулу, getMaximumPoolSize(). Максимальна кількість потоків, яку може виділити пул.

Поведінка ThreadPoolExecutorв Java, коли подається нове завдання:

  • Оскільки N <= Cдля простоїв потоків нове вхідне завдання не призначається, натомість створюється новий потік.
  • Для N > Cі якщо є простої потоків, тоді там призначається нове завдання.
  • Якщо N > Cта немає НІЯКИХ непрацюючих потоків, нові завдання ставляться до черги. ТУТ НЕ СТВОРЕНО НОВОЇ НИТКИ.
  • Коли черга заповнена , ми створюємо нові нитки до M. Якщо Mдосягнуто, ми відхиляємо завдання. Тут важливо не те, що ми не створюємо нових потоків, поки черга не заповниться!

Джерела:

Приклади

Приклад із corePoolSize = 0і maximumPoolSize = 10з місткістю черги 50.

Це призведе до одного активного потоку в пулі, доки в черзі не буде 50 елементів.

executor.execute(task #1):

before task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]

after task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]

[task #1 immediately queued and kicked in b/c the very first thread is created when `workerCountOf(recheck) == 0`]

execute(task #2):

before task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

after task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]

[task #2 not starting before #1 is done]

... executed a few tasks...

execute(task #19)

before task #19 submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 1, active threads = 1, queued tasks = 17, completed tasks = 0]

after task #19 submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 1, active threads = 1, queued tasks = 18, completed tasks = 0]

...

execute(task #51)

before task submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 1, active threads = 1, queued tasks = 50, completed tasks = 0]

after task submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 2, active threads = 2, queued tasks = 50, completed tasks = 0]

Queue is full.
A new thread was created as the queue was full.

Приклад із corePoolSize = 10і maximumPoolSize = 10з місткістю черги 50.

Це призведе до 10 активних потоків у пулі. Коли в черзі буде 50 елементів, завдання буде відхилено.

execute(task #1)

before task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]

after task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

execute(task #2)

before task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

after task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]

execute(task #3)

before task #3 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]

after task #3 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 0]

... executed a few tasks...

execute(task #11)

before task #11 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 0]

after task #11 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 10, active threads = 10, queued tasks = 1, completed tasks = 0]

... executed a few tasks...

execute(task #51)
before task #51 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 10, active threads = 10, queued tasks = 50, completed tasks = 0]

Task was rejected as we have reached `maximumPoolSize`. 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.