-
Executors Framework and Thread PoolsStaticPL/JAVA 2019. 8. 21. 20:39
1. Overview
Naive Thread management by extending the Thread class or implementing the Runnable interface has a problem when an application requires creating 20 or 30 threads for running tasks concurrently. Executors Framework is creating and managing threads for a production-ready application like hundreds, if not thousands of threads running simultaneously.
2. Description
2.1 Executor Functionality
- Thread Creation
It provides various methods for creating methods, more specifically a pool of threads, that your application can use to run task s concurrently.
- Thread management
It manages the life cycle of the threads in the thread pool. No need to worry about whether the threads in the thread pool are active or busy or dead before submitting a task for execution.
- Task submission and execution
It provides methods for submitting tasks for execution in the thread pool and also gives you the power to decide when the tasks will be executed such as scheduled to be executed later or right now or periodically.
- Thread reusability
- Min, Max number of threads.
2.2 Component
Java concurrency API defines the following three executor interfaces that covers everything that is needed for creating and managing threads
2.2.1 Executor
A simple interface that contains a method called execute() to launch a task specified by a Runnable object.
2.2.2 ExecutorService
A sub-interface of Executor that adds functionality to manage the lifecycle of the tasks. It also provides a submit() method whos overloaded version can accept a Runnable as well as a Callable object. Callable objects are similar to Runnable except that the task specified by a Callable object can also return a value.
below is an example of ExecutorService with a single worker thread, and submit a task to be executed inside the worker thread.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorsExample { public static void main(String[] args) { System.out.println("Inside : " + Thread.currentThread().getName()); System.out.println("Creating Executor Service..."); ExecutorService executorService = Executors.newSingleThreadExecutor(); System.out.println("Creating a Runnable..."); Runnable runnable = () -> { System.out.println("Inside : " + Thread.currentThread().getName()); }; System.out.println("Submit the task specified by the runnable to the executor service."); executorService.submit(runnable); } }
If a task is submitted for execution and the thread is currently busy executing another task, then the new task will wait in a queue until the thread is free to execute it. If running above program, you will notice that the program never exits, because, the executor service keeps listening for new tasks until we shut it down explicitly.
2.3 Shutdown Executor
2.3.1 shutdown()
It stops accepting new tasks, waits for previously submitted tasks to execute, and then terminates the executor.
2.3.2 shutdownNow()
This interrupts the running tasks and shut down the executor immediately.
The real power of ExecutorService comes when creating a pool of threads and execute multiple tasks concurrently in the thread pool.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class ExecutorsExample { public static void main(String[] args) { System.out.println("Inside : " + Thread.currentThread().getName()); System.out.println("Creating Executor Service with a thread pool of Size 2"); ExecutorService executorService = Executors.newFixedThreadPool(2); Runnable task1 = () -> { System.out.println("Executing Task1 inside : " + Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException ex) { throw new IllegalStateException(ex); } }; Runnable task2 = () -> { System.out.println("Executing Task2 inside : " + Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException ex) { throw new IllegalStateException(ex); } }; Runnable task3 = () -> { System.out.println("Executing Task3 inside : " + Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException ex) { throw new IllegalStateException(ex); } }; System.out.println("Submitting the tasks for execution..."); executorService.submit(task1); executorService.submit(task2); executorService.submit(task3); executorService.shutdown(); } }
A fixed thread pool is a very common type of thread pool that is frequently used in multi-threaded applications. In a fixed thread-pool, the executor service makes sure that the pool always has the specified number of threads running. if any thread dies due to some reason, it is replaced by a new thread immediately. If submitting more tasks than the available number of threads and all the threads are currently busy executing the existing tasks, then the new tasks will wait for their turn in a queue.
2.4 Thread Pool
Most of the executor implementations use thread pools to execute tasks. This is a bunch of worker threads that exist separately from the Runnable or Callable tasks and is managed by the executor.
Executor service has to create the thread pool only once and then it can reuse the threads for executing any task because creating a thread is an expensive operation.
Tasks are submitted to a thread pool via an internal queue called the Blocking Queue. If tasks are more than the number of active threads, they are inserted into the blocking queue for waiting until any thread becomes available. If the blocking queue is full than new tasks are rejected.
2.5 Type of Executors
2.5.1 ScheduledThreadPoolExecutor
Number of threads is optional
Handleable runtime exceptions if want by overriding afterExecute method from ThreadPoolExecutor
Cancels the task threw the exception while letting others continue to run
Relies on the OS scheduling system to keep track of time zones, delays, solar time, etc.
Providing collaborative API if need coordination between multiple tasks, like waiting for the completion of all tasks submitted
2.5.2 newCachedThreadPool
newCachedThreadPool going to return an executorService that can dynamically reuse threads. Before starting a job, it going to check whether there are any threads that finished the job. If there are, reuse them. If there are no waiting threads, it is going to create another one good for the processor. It's an effective solution
2.5.3 newFixedThreadPool
newFixedThreadPool maximizes the number of threads. When we want to start a job and if all the threads are busy, we have to wait for one to terminate
2.5.4 newSingleThreadExecutor
3. Example
class Worker implements Runnable{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println(i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class App { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(5); for(int i=0;i<5;i++){ executorService.execute(new Worker()); } } }
4. References
https://www.callicoder.com/java-executor-service-and-thread-pool-tutorial/
'StaticPL > JAVA' 카테고리의 다른 글
JDK, JRI, JVM, and Classloader (0) 2019.08.23 Callable (0) 2019.08.21 CompletableFuture, and Future (0) 2019.08.20 Threadlocal (0) 2019.08.19 Thread and Runnable (0) 2019.08.18