[Java Concurrency] 04: ExecutorService

Single Thread Executor

When you want to run a task, you can start a thread or you can use the Executors to create a single thread to run the task.

Consider this block:

package com.datmt.concurrency.java.executor;

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

public class D_Executor_Single {

    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        
        service.submit(() -> System.out.println("hello"));
    }
}

when you run the code, the output is what the task does:

However, you can see that the program didn’t stop (the green dot, the stop button in the screenshot). To tell a ExecutorService to stop, you need to call the shutdown method.

package com.datmt.concurrency.java.executor;

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

public class D_Executor_Single {

    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();

        try {
            service.submit(() -> System.out.println("hello"));
        } finally {
            service.shutdown();
        }
    }
}

Notice that the shutdown method is called inside the finally block. This makes sure shutdown is always called.

The shutdown method doesn’t terminate running threads. It just stops accepting new task submitted to the pool.

You can submit not one but many tasks to an ExecutorService and they will be executed in the order they are submitted.

package com.datmt.concurrency.java.executor;

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

public class D_Executor_Single {

    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();

        Runnable task1 = () -> System.out.println("Hey from runnable 1");
        Runnable task2 = () -> System.out.println("Hey from runnable 2");

        try {
            service.submit(task1);
            service.submit(task2);
        } finally {
            service.shutdown();
        }
    }

}

Output:

Multiple thread pool

If you want to have more than one thread, you can use either newFixedThreadPool or newCachedThreadPool.

newFixedThreadPool takes an integer as a parameter. This is the number of threads in the pool. A thread could be reused to execute other tasks.

newCachedThreadPool creates new threads as needed. That means when there are tasks submitted to the pool and no thread is available, new threads will be created to handle those tasks.

On the other hand, if a thread in a pool created by newCachedThreadPool is not used for 60 seconds, it will be terminated.

Execute tasks using newCachedThreadPool

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            service.submit(() -> System.out.println("Running in thread " + Thread.currentThread().getName()  ));
        }
    }

Execute tasks using newFixedThreadPool

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            service.submit(() -> System.out.println("Running in thread " + Thread.currentThread().getName()  ));
        }
    }

Can you spot the differences when executing the above two code blocks?

The first one, which uses newCachedThreadPool will terminate after 60 seconds while the second one runs forever.

That’s why it is important to call the shutdown method when you have no other tasks to submit to the pool.

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            service.submit(() -> System.out.println("Running in thread " + Thread.currentThread().getName()  ));
        }

        service.shutdown();
    }

shutdown vs shutdownNow

As mentioned above, it is important to call the shutdown method on the ExecutorService when you don’t expect more tasks to be submitted. The shutdown method does the following this:

  • Shutdowns the executor
  • Refuses new tasks to be submitted to the pool
  • Allow tasks to be complete, even if they are in waiting state

Now, in comparison to shutdown, shutdownNow is a bit more extreme:

  • Attempt to shutdown all executing tasks
  • Refuses to take new tasks
  • Do not let waiting tasks get a chance to get executed
  • Returns a list of waiting tasks

Conclusion

In this post, we walked through the usage of ExecutorService interface. It is quite convenient to spawn new threads to execute tasks using ExecutorService. In fact, it’s the preferred way to creating new Thread and call the start method.


Leave a Reply

Your email address will not be published.