Як я можу обійти це обмеження, ThreadPoolExecutor
коли чергу потрібно обмежити та заповнити, перш ніж буде запущено більше ниток.
Я вважаю, що нарешті знайшов дещо елегантне (можливо, трохи гакітне) рішення цього обмеження ThreadPoolExecutor
. Вона включає в себе розширення , LinkedBlockingQueue
щоб він повернутися false
до queue.offer(...)
коли вже є деякі завдання , поставлені в чергу. Якщо поточні потоки не йдуть у відповідь із завданнями з черги, TPE додасть додаткові потоки. Якщо пул вже на максимальних потоках, тоді RejectedExecutionHandler
буде викликано. Потім обробник put(...)
переходить у чергу.
Безумовно, дивно писати чергу, куди offer(...)
можна повертатися false
і put()
ніколи не блокувати, так що це частина злому. Але це добре працює з використанням черги TPE, тому я не бачу проблем із цим.
Ось код:
// extend LinkedBlockingQueue to force offer() to return false conditionally
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
private static final long serialVersionUID = -6903933921423432194L;
@Override
public boolean offer(Runnable e) {
// Offer it to the queue if there is 0 items already queued, else
// return false so the TPE will add another thread. If we return false
// and max threads have been reached then the RejectedExecutionHandler
// will be called which will do the put into the queue.
if (size() == 0) {
return super.offer(e);
} else {
return false;
}
}
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1 /*core*/, 50 /*max*/,
60 /*secs*/, TimeUnit.SECONDS, queue);
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// This does the actual put into the queue. Once the max threads
// have been reached, the tasks will then queue up.
executor.getQueue().put(r);
// we do this after the put() to stop race conditions
if (executor.isShutdown()) {
throw new RejectedExecutionException(
"Task " + r + " rejected from " + e);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
});
За допомогою цього механізму, коли я надсилаю завдання в чергу, ThreadPoolExecutor
буде:
- Початково масштабуйте кількість ниток до розміру серцевини (тут 1).
- Запропонуйте це до черги. Якщо черга порожня, вона буде в черзі оброблятися існуючими потоками.
- Якщо в черзі вже є 1 або більше елементів,
offer(...)
заповіт повернеться хибним.
- Якщо помилка повертається, масштабуйте кількість потоків у пулі, поки вони не досягнуть максимального числа (тут 50).
- Якщо на максимумі, то він викликає
RejectedExecutionHandler
- В
RejectedExecutionHandler
той ставить завдання в чергу для обробки першого доступного потоку в FIFO порядку.
Хоча в моєму прикладі коду вище, черга не обмежена, ви також можете визначити її як обмежену чергу. Наприклад, якщо ви додасте до 1000 ємність, LinkedBlockingQueue
то це:
- масштабуйте нитки до макс
- потім чергуйте, поки не заповниться 1000 завдань
- потім заблокуйте абонента, доки в черзі не стане місця.
Крім того, якщо вам потрібно було скористатися offer(...)
в
RejectedExecutionHandler
тоді, ви можете використовувати offer(E, long, TimeUnit)
метод замість Long.MAX_VALUE
цього часу.
Увага:
Якщо ви очікуєте, що завдання будуть додані виконавцю після його відключення, тоді ви, можливо, захочете бути розумнішими щодо викидання RejectedExecutionException
з нашого звичаю, RejectedExecutionHandler
коли служба виконавця буде відключена. Дякуємо @RaduToader за вказівку на це.
Редагувати:
Іншим підключенням до цієї відповіді може стати запитання у TPE, чи є потоки в режимі очікування, і лише піддайте елементу, якщо він є. Вам доведеться скласти для цього справжній клас і додати ourQueue.setThreadPoolExecutor(tpe);
метод.
Тоді ваш offer(...)
метод може виглядати приблизно так:
- Перевірте, чи
tpe.getPoolSize() == tpe.getMaximumPoolSize()
в такому випадку просто зателефонуйте super.offer(...)
.
- Інше, якщо
tpe.getPoolSize() > tpe.getActiveCount()
тоді зателефонувати, super.offer(...)
оскільки, здається, є порожні теми.
- В іншому випадку поверніться
false
до вилки іншої нитки.
Можливо, це:
int poolSize = tpe.getPoolSize();
int maximumPoolSize = tpe.getMaximumPoolSize();
if (poolSize >= maximumPoolSize || poolSize > tpe.getActiveCount()) {
return super.offer(e);
} else {
return false;
}
Зауважте, що методи отримання на TPE дорогі, оскільки вони отримують доступ до volatile
полів або (у випадку getActiveCount()
) блокують TPE та переходять до списку потоків. Крім того, тут є умови перегонів, які можуть спричинити неправильне введення завдання або роздвоєння іншої нитки, коли була неробоча різьба.