I know we can execute Runnable
in an ExecutorService
which will be executed as separate threads. But I recently came across a situation where I need to know the state of a Thread during its executions. For that I created instances of Thread
(instead of Runnable) and submitted it to an ExecutorService. However, the Thread state remained in NEW state. Syntactically the program had no errors but I feel I am missing something. Here is the sample program:
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);
}
};
}
}
Do not conflate tasks with threads.
That mistake was made by the designers of Thread
class. The Thread
class should never have implemented Runnable
— now generally recognized as a design flaw.
ExecutorService
implementations manage their own thread(s). Submitting a Thread
object to an ExecutorService
does not make sense.
Furthermore, you called Executor#execute
rather than ExecutorService#submit
. A task passed to execute
might execute on the current thread rather than on a background thread.
Implement your task as a class of your own device that implements Runnable
/Callable
, without any Thread
class involvement. Submit instances of your task class to an executor service.
Given the squirrelly fact that Thread
implements Runnable
, submitting a Thread
object to an executor service, does execute its run
method, but does so on a thread under the control of the executor service. Your Thread
object’s thread is unused, hence its state never changes.
Use ExecutorService
, and forget about Thread
.
I know we can execute Runnable in an ExecutorService which will be executed as separate threads.
Yes. You should define your tasks as objects implementing either Runnable
or Callable
. Then submit those objects to an ExecutorService
to be run on a background thread.
But I recently came across a situation where I need to know the state of a Thread during its executions
In modern Java, we rarely address the Thread
class directly.
The Executors framework was added in Java 5 to relieve us Java programmers of having to manage and juggle threads.
For those threads that you want to know the state of, you likely should rewrite them as Runnable
/Callable
tasks, then submit to an ExecutorService
. Use the returned Future
object to monitor their progress and success/failure. If this suggestion does not seem to fit your situation, then you should post another Question explaining that situation in detail.
For that I created instances of Thread (instead of Runnable) and submitted it to an ExecutorService.
This approach is confusing because of a very unfortunate design decision made in the earliest days of the invention of Java where they made Thread
implement Runnable
. Conflating the thread with the task has caused no end of pain and confusion over the succeeding years.
So your subclass of Thread
, sporting an implemented run
method, does indeed execute when you submit to an ExecutorService
. But I presume that the work within the run
method is not executing on your Thread
object’s thread. Whatever thread is being used by the ExecutorService
implementation is actually executing the run
method. So there is absolutely no point to you having a Thread
object in hand at all.
Again, the real solution is to rewrite your code to place the run
method within a class that merely implements Runnable
but does not confusingly subclass Thread
.
You said:
However, the Thread state remained in NEW state.
That is because your Thread
object’s thread is never doing any work. Your executor service’s thread is doing the work.
You said:
but I feel I am missing something
Indeed you are missing the fact that an executor service by definition uses its own thread(s). Submitting a Thread
object to an executor service makes no sense.
When you are waiting for a set of tasks to finish their execution in an executor service, take advantage of the fact that ExecutorService
is AutoCloseable
. That means you can use try-with-resources syntax to automatically (a) wait for completion, and (b) close the executor service implicitly.
Here is your code revised to focus on the tasks rather than the threads.
Notice that we do not pass each Runnable
to a Thread
constructor. We need no Thread
object. Instantiating and managing threads is the job of the executor service, not the calling Java programmer.
Notice that we call ExecutorService#submit
rather than Executor#execute
. Doing so ensures that we will indeed be running our task on a background thread rather than the current thread.
Notice how we capture and collect each returned Future
object. We use each Future
object to track the progress and success of the respective task. See how we interrogate those Future
object after the executor service is closed.
This example code uses an executor service backed by a single thread because you did so in the Question. So your tasks are performed sequentially, in the order of their submission. Given that the original thread is waiting for tasks to complete, using a single-threaded executor service makes no sense. We might as well have executed the tasks’ work in the original thread. For real work, explore Executors
class to find a more appropriate implementation of ExecutorService
that uses multiple threads.
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 );
}
};
}
}
When run:
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