Яка саме різниця між розміром основного пулу та максимальним розміром пулу, коли ми говоримо з точки зору ThreadPoolExecutor
?
Чи можна це пояснити за допомогою прикладу?
Яка саме різниця між розміром основного пулу та максимальним розміром пулу, коли ми говоримо з точки зору ThreadPoolExecutor
?
Чи можна це пояснити за допомогою прикладу?
Відповіді:
Візьмемо цей приклад. Початковий розмір пулу потоків - 1, розмір основного пулу - 5, максимальний розмір пулу - 10, а черга - 100.
Коли надходять запити, потоків буде створюватися до 5, а потім завдання будуть додаватися до черги, поки вона не досягне 100. Коли черга заповнена, нові потоки будуть створені до
maxPoolSize
. Як тільки всі потоки будуть використані, і черга заповнена, завдання будуть відхилені. Зі зменшенням черги зменшується і кількість активних потоків.
allowCoreThreadTimeOut(boolean)
який дозволяє вбити потоки стрижня після заданого часу простою. Якщо встановити для цього значення true і setting core threads
= max threads
дозволяє пулу потоків масштабуватись від 0 до max threads
.
ЯКЩО запущені потоки> 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:
Якщо кількість потоків менше, ніж corePoolSize, створіть нову тему для запуску нового завдання.
Якщо кількість потоків дорівнює (або перевищує) corePoolSize, поставте завдання в чергу.
Якщо черга заповнена, а кількість потоків менше maxPoolSize, створіть новий потік для виконання завдань.
Якщо черга заповнена, а кількість потоків більше або дорівнює maxPoolSize, відхиліть завдання.
Сподіваюся, це HelpFul .. і, будь ласка, виправте мене, якщо я помиляюся ...
Від док .
Коли нове завдання подається у виконанні методу (java.lang.Runnable), і працює менше потоків corePoolSize, створюється новий потік для обробки запиту, навіть якщо інші робочі потоки простоюють. Якщо запущено більше, ніж corePoolSize, але менше максимального потокуPoolSize, новий потік буде створений, лише якщо черга заповнена.
Крім того:
Встановивши corePoolSize і maximumPoolSize однаково, ви створюєте пул потоків фіксованого розміру. Установивши maxPoolSize по суті необмежене значення, таке як Integer.MAX_VALUE, ви дозволяєте пулу вміщувати довільну кількість одночасних завдань. Найчастіше розмір ядра та максимальний розмір пулу встановлюється лише під час побудови, але їх також можна динамічно змінювати за допомогою setCorePoolSize (int) та setMaximumPoolSize (int).
Якщо ви вирішили створити ThreadPoolExecutor
вручну замість використання Executors
заводського класу, вам потрібно буде створити та налаштувати його за допомогою одного з його конструкторів. Найбільш обширний конструктор цього класу:
public ThreadPoolExecutor(
int corePoolSize,
int maxPoolSize,
long keepAlive,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler
);
Як бачите, ви можете налаштувати:
Обмеження кількості паралельних завдань, що виконуються, розмір пулу потоків представляє величезну вигоду для вашої програми та її середовища виконання з точки зору передбачуваності та стабільності: необмежене створення потоку в кінцевому підсумку вичерпає ресурси середовища виконання, і як наслідок це може спричинити ваша програма , серйозні проблеми з продуктивністю, які можуть призвести навіть до нестабільності програми.
Це вирішення лише однієї частини проблеми: ви обмежуєте кількість виконуваних завдань, але не обмежуєте кількість завдань, які можна подати та поставити в чергу для подальшого виконання. У додатку пізніше виникне дефіцит ресурсів, але він з часом випробує його, якщо рівень подання послідовно перевищує швидкість виконання.
Рішення цієї проблеми полягає в: наданні виконавцю черги блокування для утримання завдань, що очікують. У разі заповнення черги надіслане завдання буде "відхилено". TheRejectedExecutionHandler
Викликається , коли уявлення завдання відкидається, і саме тому дієслово відкидається був цитованих в попередньому пункті. Ви можете реалізувати власну політику відхилення або скористатися однією із вбудованих політик, передбачених фреймворком.
За замовчуванням у політиці відхилення виконавець кидає a RejectedExecutionException
. Однак інші вбудовані політики дозволяють вам:
Правила розміру пулу ThreadPoolExecutor
Правила розміру a ThreadPoolExecutor's
пулу, як правило, неправильно зрозумілі, оскільки він працює не так, як ви вважаєте, що потрібно, або так, як ви хочете.
Візьмемо цей приклад. Початковий розмір пулу потоків - 1, розмір основного пулу - 5, максимальний розмір пулу - 10, а черга - 100.
Шлях Сонця: оскільки запити надходять у потоках, їх буде створено до 5, тоді завдання будуть додаватися до черги, поки вона не досягне 100. Коли черга заповнена, нові потоки будуть створюватися до maxPoolSize
. Як тільки всі потоки будуть використані, а черга заповнена, завдання буде відхилено. Зі зменшенням черги зменшується і кількість активних потоків.
Спосіб, передбачений користувачем: оскільки запити, що надходять у потоках, будуть створюватися до 10, тоді завдання будуть додаватися до черги, поки не досягне 100, і тоді вони будуть відхилені. Кількість потоків буде перейменовано на максимум, поки черга не порожня. Коли черга порожня, потоки відмирають, доки не corePoolSize
залишиться.
Різниця полягає в тому, що користувачі хочуть почати збільшувати розмір пулу раніше і хочуть, щоб черга була меншою, де як метод Sun хоче зберегти розмір пулу невеликим і збільшити його лише після того, як навантаження стане значним.
Ось правила Sun для створення ниток простими словами:
corePoolSize
, створіть нову тему для запуску нового завдання.corePoolSize
, поставте завдання в чергу.maxPoolSize
, створіть новий потік для запуску завдань.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
Ви можете знайти визначення термінів corepoolsize та maxpoolsize в javadoc. http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html
У наведеному вище посиланні є відповідь на ваше запитання. Однак лише для того, щоб це було зрозуміло. Додаток буде продовжувати створювати потоки, поки не дійде до corePoolSize. Я думаю, що ідея тут полягає в тому, що цих багатьох потоків має бути достатньо для обробки припливу завдань. Якщо нове завдання з’являється після створення потоків corePoolSize, завдання потрапляють у чергу. Як тільки черга заповниться, виконавець почне створювати нові потоки. Це свого роду балансування. По суті, це означає, що приплив завдань перевищує обробну потужність. Отже, Executor знову почне створювати нові потоки, поки не досягне максимальної кількості потоків. Знову ж таки, нові потоки будуть створені тоді і тільки тоді, коли черга заповнена.
Хороше пояснення в цьому блозі:
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
З книги Основи забезпечення безпеки Java :
CorePoolSize : ThreadPoolExecutor має атрибут corePoolSize, який визначає, скільки потоків він почнеться, поки нові потоки не будуть запущені лише тоді, коли черга заповнена
MaximumPoolSize : Цей атрибут визначає, скільки потоків було запущено максимум. Ви можете встановити це ціле число. MAX_VALUE, щоб не було верхньої межі
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);
}
Розуміння внутрішньої поведінки, 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`.