Question about the Executors.newSingleThreadExecutor()

Viewed 570

This is a question regarding program flow of the following code:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
    public static void main(String args[]) {
        ExecutorService service = null;
        try {
            service = Executors.newSingleThreadExecutor();
            
            System.out.println("begin");
            service.execute(() -> System.out.println("Printing zoo inventory"));
            service.execute(() -> {
                for(int i = 0; i< 3; i++)
                    System.out.println("Printing record: " + i);
            });
            service.execute(() -> System.out.println("printing zoo inventory"));
        } finally {
            if(service != null)
                service.shutdown();
        }
    }
}

In the code above, once we cross the line with "System.out.println("begin")...the thread executor does each following action task (basically a Runnable lambda) one after the other.

My understanding is that these "tasks" (the s.o.p("printing..."), for-loop, s.o.p("printing..")) are going to be run one after the other considering the thread executor "service" executes each Runnable lambda on the thread executor.

Am i correct that the program flow will not move to the next line unless the Runnable lambda is finished on current line? For example, unless the Runnable lambda finishes for s.o.p("printing zoo inventory")...it wont start the next Runnable lambda on next line?

What happens in situation if the current thread executor line with a Runnable lambda that is computationally intensive? In this case, the next line (if it contains another Runnable lambda)..would have to wait until the current thread executor finishes the task?

1 Answers

Q & A

-"Am i correct that the program flow will not move to the next line unless the unnable lambda is finished on current line? For example, unless the Runnable lambda finishes for s.o.p("printing zoo inventory")...it wont start the next Runnable lambda on next line?"

- Yes, you are correct.*

-"What happens in situation if the current thread executor line with a Runnable lambda that is computationally intensive? In this case, the next line (if it contains another Runnable lambda)..would have to wait until the current thread executor finishes the task?"

- Yes, it would have to wait.*

-"Your favorite color?"

-Red.

( * ) Assuming you are checking the behaviour from the ExecutorService, leaving out of the equation the main thread (and its invocation in order to execute something), and focusing on the pooled worker threads. The main thread runs by its own, and some implementations may also assign tasks to it if they decide to, that's why this note.



Test & Compare

I will try to compare this executor with a multithreaded one, in order to show the differences between both approaches.

Regarding your questions, which focus on the blocking-waiting scenario, the answers given differ completely based on the used executor service. Using the second option, the answers would be:

  • It will be able to move to the next line if a worker is avaliable, even if current line didn't finish.

  • No, it won't have to wait if a thread is avaliable.

  • Red.

SingleThreadExecutor

Creates an Executor that uses a single worker thread operating off an unbounded queue. Tasks are guaranteed to execute sequentially, and no more than one task will be active at any given time.

You only have a single thread on the pool, so all the tasks are assigned to it. It will have to sequentially execute them all, as stated on the docs. To make a simple test, for example:

service = Executors.newSingleThreadExecutor();
        
service.execute(() -> System.out.println("Printing zoo inventory"));
service.execute(() -> {
                        try {Thread.sleep(5000);    System.out.println("Woke up"); }
                        catch (InterruptedException e) {e.printStackTrace();}
                      });
service.execute(() -> System.out.println("Finish"));

Output

  Printing zoo inventory     // ---- [Thread 1] 
  //...5s 
  Woke up                    // ---- [Thread 1]
  Finish                     // ---- [Thread 1] -{x}- 

As shown in this at least mediocre time graph:

   {task1}    {task2}                          {task3}
      ^          ^                                ^
      |          |                     (~5s)      |
  [Thread1]-->[Thread1]---------------------->[Thread1]->{x}   

Debugging it, the only avaliable thread affirmes it was the one that executed the previous two tasks. The image examples are from original OP's question:

enter image description here

Breakpoint at third task - already two completed

enter image description here


MultiThreaded Pool

Using the FixedThreadPool for the example. The behaviour of the process changes when more than one worker thread is avaliable; For this example, two threads are set.

As always, the docs should be read carefully:

enter image description here

Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. At any point, at most nThreads threads will be active processing tasks. If additional tasks are submitted when all threads are active, they will wait in the queue until a thread is available

Modified the test a little bit, to add some info regarding the working threads and perform some extra tasks.

volatile boolean wifeAlarm = false;
//...
service = Executors.newFixedThreadPool(2);

Once set, execute multiple tasks:

service.execute(() -> System.out.println("Woke up fast -" 
                      + Thread.currentThread().getName()));
service.execute(() -> 
{ 
  try {
         Thread.sleep(5000);    
         System.out.println("Woke up lazy - John where are you?? - {"+ 
                            Thread.currentThread().getName()+"}"); 
      } catch (InterruptedException e){}
       finally { wifeAlarm=true;}
 });
 service.execute(() -> System.out.println("Cleaning - {"
                       + Thread.currentThread().getName()+"}"));
 service.execute(() -> System.out.println("Making breakfast - {"
                       +Thread.currentThread().getName()+"}"));
 service.execute(() -> System.out.println("Flirt with neighbour - {"
                       +Thread.currentThread().getName()+"}"));
 service.execute(() -> System.out.println("Got her number - {"
                       +Thread.currentThread().getName()+"}"));
 service.execute(() -> System.out.println("Send hot af pic - {"
                       +Thread.currentThread().getName()+"}"));
 service.execute(() -> System.out.println("Remove all proof on phone - {"
                       +Thread.currentThread().getName()+"}"));
 service.execute(() -> 
 {
   try {
         while (!wifeAlarm)
             Thread.sleep(13);
         System.out.println("Just working my love - {"+ 
                            Thread.currentThread().getName()+"}"); 
       } catch (InterruptedException e) {}
  });

Output

Woke up fast - {pool-1-thread-1}
Cleaning - {pool-1-thread-1}
Making breakfast - {pool-1-thread-1}
Flirt with neighbour - {pool-1-thread-1}
Got her number - {pool-1-thread-1}
Send hot af pic - {pool-1-thread-1}
Remove all proof on phone - {pool-1-thread-1}
// ~4-5s
Woke up lazy - John where are you?? - {pool-1-thread-2}
Just working my love - {pool-1-thread-1}

This is just another terrible representation:

   {task1} {task2}  {task3}  (..)  {task9}
      ^       ^        ^              ^                     (~5s)
      |   [Thread2]--- | -------------|---------(...)----------->{x}        
  [Thread1] ----->[Thread1]--(..)-[Thread1]----------------------->{x}   
              

In resume: John has taken advantage of this new context, achieving something great thanks to the multithreaded pool.

enter image description here

John is able to execute 7 actions while thread-2 executes 1. Even better for John, he's able to finish them all before thread-2 finished its assigned task. John is safe now, it will finish its task and go IDLE, as the queue is empty. Good for John.

  • thread-2 is assigned task 2. But this time the sleep won't lead to an increase on the work queue, as the other thread is able to concurrently execute them.
  • thread-1 executes all the 4 vital tasks: 5, 6, 7 and 8. It is also assigned the other 4 low priority tasks, being able to empty the work queue while the other thread was "busy" (sleeping).
Related