Я создаю пул потоков Java, используя FixedThreadPool с размером пула 10.
Из конструктора это
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Это означает, что я создал пул потоков с размером основного пула потоков 10 и максимальным размером пула потоков 10. Согласно официальному документу Java 8, это означает, что:
Однако при проверке счетчиков меня смутил следующий счетчик:
ActiveCount = 0PoolSize = 10QueueSize = 1Вышеупомянутый счетчик появляется несколько раз, так что это не единичная ситуация. Я также вижу такие счетчики:
ActiveCount = 1PoolSize = 10QueueSize = 1В этом случае запрос все еще находится в очереди, пока количество потоков, активно обрабатывающих задачи, меньше размера пула. Кто-нибудь знает, почему это происходит? Я что-то упускаю здесь?
@markspace Спасибо, что повторили эту проблему. Вы также видите, что количество активных действий в этом случае очень низкое? Что для меня не имеет смысла, так это активный счет: я всегда чувствую, что он может быть существенно выше.
Да, я могу поставить 20 задач в очередь и иногда иметь только 2 активных потока. Я думаю, это также связано со временем, которое занимает каждая задача. Я думаю, что очень короткие задачи (например, микротест или, возможно, плохо спроектированное приложение) могут закончиться так быстро, что более поздним задачам будет назначен существующий поток, а не увеличение количества потоков. Я только предполагаю, но я чувствую, что это может сработать.
В поддержку этой идеи я добавил Thread.sleep(2000) в свой тестовый код, и тогда все 10 потоков в исполнителе присутствуют.
Я действительно не вижу здесь проблемы. Вы предлагаете не создавать очереди, пока количество активных участников меньше 10? Как вы это проверяете? Ничто из того, что вы показали в javadoc, не противоречит этому. Задача добавляется в очередь, затем поток берет ее из очереди и работает над ней.




Я не знаю, является ли это «хорошим» ответом, но я добавлю его, поскольку он кажется немного неочевидным.
Когда я тестирую код ОП с помощью очень короткого тестового примера (задачи), я получаю результаты, аналогичные результатам ОП. Это можно считать распространенной проблемой микротестов. Или это также может быть проблема с плохо разработанным производственным кодом, отправляющим очень короткие задачи для выполнения в отдельный поток. Например:
public static void main( String[] args ) {
ExecutorService es = Executors.newFixedThreadPool( 10 );
System.out.println( es );
for( int i = 0; i < 20; i++ )
es.execute( () -> {System.out.println( "test" ); } );
// es.execute( FixedThreadPool::test );
System.out.println( es );
es.shutdown();
}
Выдает такой вывод:
test
test
test
java.util.concurrent.ThreadPoolExecutor@7506e922[Running, pool size = 10, active threads = 2, queued tasks = 10, completed tasks = 8]
test
test
test (more "test" lines removed)
С другой стороны, если я добавлю к тестовому заданию более длительную задержку, например, 2-секундную задержку сна, то все 10 потоков в исполнителе будут отображаться как активные.
public class FixedThreadPool {
public static void main( String[] args ) {
ExecutorService es = Executors.newFixedThreadPool( 10 );
System.out.println( es );
for( int i = 0; i < 20; i++ )
es.execute( FixedThreadPool::test );
System.out.println( es );
es.shutdown();
}
private static void test() {
try {
System.out.println( "test" );
Thread.sleep( 2000 );
} catch( InterruptedException ex ) {
// exit
}
}
}
Выход:
test
test
java.util.concurrent.ThreadPoolExecutor@7506e922[Running, pool size = 10, active threads = 10, queued tasks = 10, completed tasks = 0]
test
test
test (more "test" lines trimmed)
Спасибо, Марк! Я думаю, это имеет смысл. Метрика, которую я задал в вопросе, - это в секунду. Я провел некоторые расчеты и обнаружил, что статистически возможно, что каждый раз, когда выдается счетчик, активных задач не возникает. Ваше расследование действительно очень помогает здесь!
Это артефакт из-за условий гонки.
Если вы посмотрите на пример, предоставленный markspace:
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
System.out.println(es);
for (int i = 0; i < 20; i++) {
es.execute(() -> System.out.println("test"));
}
System.out.println(es);
es.shutdown();
}
Он создает фиксированный пул потоков размером 10, а затем отправляет 20 задач в короткой последовательности.
Для первых 10 задач рабочие потоки создаются и запускаются немедленно, остальные 10 задач добавляются в рабочую очередь.
Создаются 10 рабочих потоков, о чем свидетельствует «размер пула = 10»:
Когда вы распечатываете состояние пула потоков с помощью System.out.println(es); метода пулов потоков toString()собирает некоторую статистику:
Возвращает строку, идентифицирующую этот пул, а также его состояние, включая сведения о состоянии выполнения и предполагаемом количестве рабочих и задач.
Эти значения неточны, поскольку метод toString() не приостанавливает ни один исполнитель во время сбора статистики.
Спасибо за объяснение, Томас! «Оценочная» часть — это то, что я пропустил.
Я могу дублировать проблему, добавляя потоки настолько быстро, что это каким-то образом перегружает входную очередь. Каким бы ни был механизм «запуска», кажется, что сначала задачи ставятся в очередь, а затем проверяются, нужен ли поток для поставленной в очередь задачи. Синхронизация там может настолько замедлить работу, что новые задачи будут сохраняться на некоторое время, пока исполнитель запускает новый поток. Я предполагаю, но это то, что я получил.