Java ThreadPoolExecutor — фиксированныйThreadPool ActiveCount = 0, QueueSize = 1, PoolSize = 10

Я создаю пул потоков Java, используя FixedThreadPool с размером пула 10.

Из конструктора это

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

Это означает, что я создал пул потоков с размером основного пула потоков 10 и максимальным размером пула потоков 10. Согласно официальному документу Java 8, это означает, что:

  1. Будет максимум 10 потоков, независимо от количества поступающих запросов.
  2. Очередь будет расти бесконечно

Однако при проверке счетчиков меня смутил следующий счетчик:

  • ActiveCount = 0
  • PoolSize = 10
  • QueueSize = 1

Вышеупомянутый счетчик появляется несколько раз, так что это не единичная ситуация. Я также вижу такие счетчики:

  • ActiveCount = 1
  • PoolSize = 10
  • QueueSize = 1

В этом случае запрос все еще находится в очереди, пока количество потоков, активно обрабатывающих задачи, меньше размера пула. Кто-нибудь знает, почему это происходит? Я что-то упускаю здесь?

Я могу дублировать проблему, добавляя потоки настолько быстро, что это каким-то образом перегружает входную очередь. Каким бы ни был механизм «запуска», кажется, что сначала задачи ставятся в очередь, а затем проверяются, нужен ли поток для поставленной в очередь задачи. Синхронизация там может настолько замедлить работу, что новые задачи будут сохраняться на некоторое время, пока исполнитель запускает новый поток. Я предполагаю, но это то, что я получил.

markspace 19.04.2024 21:44

@markspace Спасибо, что повторили эту проблему. Вы также видите, что количество активных действий в этом случае очень низкое? Что для меня не имеет смысла, так это активный счет: я всегда чувствую, что он может быть существенно выше.

Major 20.04.2024 01:05

Да, я могу поставить 20 задач в очередь и иногда иметь только 2 активных потока. Я думаю, это также связано со временем, которое занимает каждая задача. Я думаю, что очень короткие задачи (например, микротест или, возможно, плохо спроектированное приложение) могут закончиться так быстро, что более поздним задачам будет назначен существующий поток, а не увеличение количества потоков. Я только предполагаю, но я чувствую, что это может сработать.

markspace 20.04.2024 03:59

В поддержку этой идеи я добавил Thread.sleep(2000) в свой тестовый код, и тогда все 10 потоков в исполнителе присутствуют.

markspace 20.04.2024 04:02

Я действительно не вижу здесь проблемы. Вы предлагаете не создавать очереди, пока количество активных участников меньше 10? Как вы это проверяете? Ничто из того, что вы показали в javadoc, не противоречит этому. Задача добавляется в очередь, затем поток берет ее из очереди и работает над ней.

matt 20.04.2024 12:43
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
5
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Когда я тестирую код ОП с помощью очень короткого тестового примера (задачи), я получаю результаты, аналогичные результатам ОП. Это можно считать распространенной проблемой микротестов. Или это также может быть проблема с плохо разработанным производственным кодом, отправляющим очень короткие задачи для выполнения в отдельный поток. Например:

   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)

Спасибо, Марк! Я думаю, это имеет смысл. Метрика, которую я задал в вопросе, - это в секунду. Я провел некоторые расчеты и обнаружил, что статистически возможно, что каждый раз, когда выдается счетчик, активных задач не возникает. Ваше расследование действительно очень помогает здесь!

Major 22.04.2024 18:54
Ответ принят как подходящий

Это артефакт из-за условий гонки.

Если вы посмотрите на пример, предоставленный 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»:

  • Перед отправкой первой задачи при печати состояния пула потоков выводится «размер пула = 0».
  • После отправки задач распечатайте статус пула потоков, выведите «размер пула = 10».

Когда вы распечатываете состояние пула потоков с помощью System.out.println(es); метода пулов потоков toString()собирает некоторую статистику:

Возвращает строку, идентифицирующую этот пул, а также его состояние, включая сведения о состоянии выполнения и предполагаемом количестве рабочих и задач.

Эти значения неточны, поскольку метод toString() не приостанавливает ни один исполнитель во время сбора статистики.

  • Работник, выполняющий задачу в данный момент, считается «активным».
  • Работник, завершивший задачу и получающий следующую задачу, не активен.
  • цикл по списку рабочих выполняется быстрее, чем рабочие, извлекающие задачи из очереди задач (поскольку цикл по списку не требует блокировки, выборка задач из очереди задач требует блокировки) Поэтому некоторые рабочие процессы, которые только что завершили задачу и находятся в состоянии получения следующей задачи, не будут отправлены.

Спасибо за объяснение, Томас! «Оценочная» часть — это то, что я пропустил.

Major 22.04.2024 18:56

Другие вопросы по теме