Я знаю, что мы можем выполнить 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);
}
};
}
}
@LouisWasserman Поскольку Thread.start() заставляет JVM выполнять метод запуска интерфейса Runnable, поэтому я ожидал того же и при использовании ExecutorService
.
Я бы сделал что-то вроде объявления своего собственного класса, который реализует Runnable, а также предоставляет методы для запроса о его прогрессе или даже обратный вызов, который он будет вызывать, чтобы информировать наблюдателя о его прогрессе. Затем передайте их экземпляры исполнителю и либо сохраните ссылки на них и вызовите их, либо зарегистрируйте с ними обратный вызов и прослушивайте события, которые они публикуют.
«Поскольку Thread.start() заставляет JVM выполнять метод запуска интерфейса Runnable». Вы хотели, чтобы все было наоборот. Это не так.
Вы не запускаете ни одного нового потока, используйте service.execute(getRunnable())
, чтобы задача выполнялась в потоке, предоставленном исполнителем.
Службы ExecutorServices обычно управляют своими собственными потоками. Они не принимают другие темы.
Вам не хватает того, что каждый поток является Runnable, и его метод run()
делегирует runnable, переданный в конструкторе. Таким образом, исполнитель запускает Thread.run() в собственном потоке одной из служб исполнителя.
Вам нужно знать состояние потока или состояние задачи? В последнем случае используйте один из методов ExecutorService::submit
и запросите возвращенный Future
. Хотя, если вам нужна специальная информация о ходе выполнения задачи, сделайте то, что описывает Дэвид в своем комментарии.
Не объединяйте задачи с потоками.
Эту ошибку допустили дизайнеры 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
Я бы не ожидал, что вы сможете добиться чего-либо, отправив поток в службу-исполнитель. Я вообще не ожидал, что это сработает.