javamultithreading

Execute an instance of Thread in an ExecutorService


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);
            }
        };
    }
}

Solution

  • tl;dr

    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.

    Details

    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.

    Example code

    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