Выполнить экземпляр Thread в ExecutorService

Я знаю, что мы можем выполнить Runnable в ExecutorService, который будет выполняться как отдельные потоки. Но недавно я столкнулся с ситуацией, когда мне нужно знать состояние потока во время его выполнения. Для этого я создал экземпляры Thread (вместо Runnable) и отправил их в ExecutorService. Однако состояние потока осталось в состоянии НОВОЕ. Синтаксически в программе не было ошибок, но я чувствую, что что-то упускаю. Вот пример программы:

public class ThreadStates3 {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(getRunnable());
        Thread thread2 = new Thread(getRunnable());

        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(thread);
        service.execute(thread2);

        while (true) {
            System.out.println("Thread 1: " + thread.getState() + ", Thread 2: " + thread2.getState());
            Thread.sleep(1000);
            boolean taskComplete = thread.getState() == Thread.State.TERMINATED &&
                    thread2.getState() == Thread.State.TERMINATED;
            if (taskComplete) break;
        }

        System.out.println("Program complete.");
    }

    static Runnable getRunnable() {
        return () -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
    }
}

Я бы не ожидал, что вы сможете добиться чего-либо, отправив поток в службу-исполнитель. Я вообще не ожидал, что это сработает.

Louis Wasserman 28.06.2024 21:45

@LouisWasserman Поскольку Thread.start() заставляет JVM выполнять метод запуска интерфейса Runnable, поэтому я ожидал того же и при использовании ExecutorService.

sam 28.06.2024 22:28

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

David Conrad 28.06.2024 22:49

«Поскольку Thread.start() заставляет JVM выполнять метод запуска интерфейса Runnable». Вы хотели, чтобы все было наоборот. Это не так.

Louis Wasserman 28.06.2024 22:56

Вы не запускаете ни одного нового потока, используйте service.execute(getRunnable()), чтобы задача выполнялась в потоке, предоставленном исполнителем.

DuncG 28.06.2024 23:08

Службы ExecutorServices обычно управляют своими собственными потоками. Они не принимают другие темы.

Louis Wasserman 28.06.2024 23:25

Вам не хватает того, что каждый поток является Runnable, и его метод run() делегирует runnable, переданный в конструкторе. Таким образом, исполнитель запускает Thread.run() в собственном потоке одной из служб исполнителя.

DuncG 28.06.2024 23:27

Вам нужно знать состояние потока или состояние задачи? В последнем случае используйте один из методов ExecutorService::submit и запросите возвращенный Future. Хотя, если вам нужна специальная информация о ходе выполнения задачи, сделайте то, что описывает Дэвид в своем комментарии.

Slaw 29.06.2024 01:23
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
8
90
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

вр; доктор

Не объединяйте задачи с потоками.

Эту ошибку допустили дизайнеры Thread класса. Класс Thread никогда не должен был реализовывать Runnable — сейчас это общепризнанный недостаток дизайна.

ExecutorService реализации управляют своими собственными потоками. Отправка объекта Thread в ExecutorService не имеет смысла.

Кроме того, вы вызвали Executor#execute , а не ExecutorService#submit. Задача, переданная execute, может выполняться в текущем потоке, а не в фоновом потоке.

Реализуйте свою задачу как класс собственного устройства, реализующего Runnable/Callable, без какого-либо участия Thread класса. Отправьте экземпляры класса задач в службу исполнителя.

Учитывая тот неожиданный факт, что Thread реализует Runnable, отправка объекта Thread в службу-исполнитель выполняет его метод run, но делает это в потоке под контролем службы-исполнителя. Поток вашего объекта Thread не используется, поэтому его состояние никогда не меняется.

Используйте ExecutorService и забудьте о Thread.

Подробности

Я знаю, что мы можем выполнить Runnable в ExecutorService, который будет выполняться как отдельные потоки.

Да. Вы должны определить свои задачи как объекты, реализующие либо Runnable, либо Callable. Затем отправьте эти объекты в ExecutorService для запуска в фоновом потоке.

Но недавно я столкнулся с ситуацией, когда мне нужно знать состояние потока во время его выполнения.

В современной Java мы редко обращаемся к классу Thread напрямую.

Платформа Executors была добавлена ​​в Java 5, чтобы избавить нас, Java-программистов, от необходимости управлять потоками и манипулировать ими.

Для тех тредов, о состоянии которых вы хотите узнать, вам, вероятно, придется переписать их как задачи Runnable/Callable, а затем отправить в ExecutorService. Используйте возвращенный объект Future, чтобы отслеживать их прогресс и успех/неуспех. Если это предложение не соответствует вашей ситуации, вам следует опубликовать еще один Вопрос, подробно объясняющий эту ситуацию.

Для этого я создал экземпляры Thread (вместо Runnable) и отправил их в ExecutorService.

Этот подход сбивает с толку из-за очень неудачного дизайнерского решения, принятого на заре изобретения Java, которое пришлось Thread реализовать Runnable. Объединение этой темы с задачей на протяжении последующих лет не приносило конца боли и замешательству.

Таким образом, ваш подкласс Thread, имеющий реализованный метод run, действительно выполняется, когда вы отправляете ExecutorService. Но я предполагаю, что работа метода run не выполняется в потоке вашего объекта Thread. Какой бы поток ни использовался реализацией ExecutorService, на самом деле выполняется метод run. Так что нет никакого смысла держать в руках предмет Thread.

Опять же, реальное решение — переписать код, чтобы поместить метод run в класс, который просто реализует Runnable, но не создает путаницы в подклассе Thread.

Вы сказали:

Однако состояние потока осталось в состоянии НОВОЕ.

Это потому, что поток вашего объекта Thread никогда не выполняет никакой работы. Поток вашей службы-исполнителя выполняет всю работу.

Вы сказали:

но я чувствую, что что-то упускаю

Действительно, вы упускаете из виду тот факт, что служба-исполнитель по определению использует свои собственные потоки. Отправка объекта Thread в сервис-исполнитель не имеет смысла.

Когда вы ожидаете завершения выполнения набора задач в сервисе-исполнителе, воспользуйтесь тем, что ExecutorService есть AutoCloseable. Это означает, что вы можете использовать синтаксис try-with-resources для автоматического (а) ожидания завершения и (б) неявного закрытия службы исполнителя.

Пример кода

Вот ваш код, измененный, чтобы сосредоточиться на задачах, а не на потоках.

Обратите внимание, что мы не передаем каждый Runnable конструктору Thread. Нам не нужен никакой Thread объект. Создание экземпляров потоков и управление ими — это работа службы-исполнителя, а не вызывающего Java-программиста.

Обратите внимание, что мы вызываем ExecutorService#submit, а не Executor#execute. Это гарантирует, что мы действительно будем выполнять нашу задачу в фоновом потоке, а не в текущем потоке.

Обратите внимание, как мы фиксируем и собираем каждый возвращенный Future объект. Мы используем каждый объект Future для отслеживания хода выполнения и успеха соответствующей задачи. Посмотрите, как мы опрашиваем этот Future объект после закрытия службы исполнителя.

В этом примере кода используется служба-исполнитель, поддерживаемая одним потоком, поскольку вы это сделали в Вопросе. Таким образом, ваши задачи выполняются последовательно, в порядке их подачи. Учитывая, что исходный поток ожидает завершения задач, использование однопоточного сервиса-исполнителя не имеет смысла. С таким же успехом мы могли бы выполнить работу задач в исходном потоке. Для реальной работы изучите класс Executors, чтобы найти более подходящую реализацию ExecutorService, использующую несколько потоков.

public class ThreadStates3
{

    public static void main ( String[] args ) throws InterruptedException
    {
        System.out.println( "BEWARE: Output from `System.out` does NOT necessarily appear in chronological order when called across threads. " );
        System.out.println( "Program start at " + Instant.now( ) );

        Runnable task1 = ThreadStates3.getRunnable( );
        Runnable task2 = ThreadStates3.getRunnable( );

        List < Future < ? > > futures = new ArrayList <>( 2 );
        try (
                ExecutorService service = Executors.newSingleThreadExecutor( ) ;
        )
        {
            Future < ? > future1 = service.submit( task1 );
            Future < ? > future2 = service.submit( task2 );
            futures.addAll( List.of( future1 , future2 ) );
        }
        // Flow-of-control blocks here until all submitted tasks are executed.
        futures.forEach( future -> System.out.println( "Task was cancelled: " + future.isCancelled( ) + " | Task done: " + future.isDone( ) + ". | " + Instant.now( ) ) );

        System.out.println( "Program complete at " + Instant.now( ) );
    }

    static Runnable getRunnable ( )
    {
        return ( ) -> {
            try
            {
                Thread.sleep( 2000 );
                System.out.println( "Task running at " + Instant.now( ) );
            } catch ( InterruptedException e )
            {
                throw new RuntimeException( e );
            }
        };
    }
}

При запуске:

BEWARE: Output from `System.out` does NOT necessarily appear in chronological order when called across threads. 
Program start at 2024-06-29T02:58:39.446949Z
Task running at 2024-06-29T02:58:41.454670Z
Task running at 2024-06-29T02:58:43.458283Z
Task was cancelled: false | Task done: true. | 2024-06-29T02:58:43.459038Z
Task was cancelled: false | Task done: true. | 2024-06-29T02:58:43.462812Z
Program complete at 2024-06-29T02:58:43.462856Z

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