- Thread Constructor(new Thread()) takes runnable as argument but not callable
- Thread instance can use either start() or run() methods to start execution of thread. start() method spawns new thread other than main to complete execution whereas run method uses the main thread to carry out execution.
- Managing Thread lifecycle is difficult hence ExecutionService framework was introduced
- When you run thread using ExecutionService it takes either instance of Ruunal
Thread Implementation Using Traditional Threads- Old Way
Task.java
public class Task implements Runnable{ @Override public void run() { System.out.println("Hello from Task.class, executed by " + currentThread().getName() + " thread"); } }
Main.java
public class Main { public static void main(String[] args) { Thread thread = new Thread(new Task()); thread.run(); } }
Output
Hello from Task.class, executed by main thread
Thread Implementation Using ExecutionService – New Way
ExecutorService is an interface in Java that provides a higher-level replacement for using raw threads. It manages a pool of threads to handle asynchronous tasks more efficiently, without the need to manually create or manage individual threads.
Simple Program using Executor Service taking Runnable as Argument
ExecutorService is a framework which allows to create thread. Threads can be created from FixedThreadPool, CachedThreadPool and ScheduledThreadPool. submit() method takes runnable or callable object (Functional Interface Type) as argument.
Main.java
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3); executorService.submit(new Task()); } }
Output
Hello from Task.class, executed by pool-1-thread-1 thread
Why we need ExecutorService when we can directly call Thread using run() or start() method?
Problem with using old Thread Methods
Poor Resource Management i.e. It keep on creating new resource for every request. No limit to creating resource. Using Executor framework we can reuse the existing resources and put limit on creating resources.
Not Robust : If we keep on creating new thread we will get StackOverflowException exception consequently our JVM will crash.
Overhead Creation of time : For each request we need to create new resource. To creating new resource is time consuming. i.e. Thread Creating > task. Using Executor framework we can get built in Thread Pool.
- Use of Thread Pool reduces response time by avoiding thread creation during request or task processing.
- Use of Thread Pool allows you to change your execution policy as you need. you can go from single thread to multiple thread by just replacing ExecutorService implementation.
- Thread Pool in Java application increases stability of system by creating a configured number of threads decided based on system load and available resource.
- Thread Pool frees application developer from thread management stuff and allows to focus on business logic.
Runnable vs Callable
Using Runnable doesn’t returns any value whereas callable returns value to the main thread
HelloThread1.java
public class TaskRunnable implements Runnable{ @Override public void run() { System.out.println("Hello from "+ this.getClass().getSimpleName() + ", executed by " + currentThread().getName() + " thread"); } }
HelloThread2.java
public class TaskCallable implements Callable { @Override public String call() throws Exception { System.out.println("Hello from "+ this.getClass().getSimpleName() + ", executed by " + currentThread().getName() + " thread"); return "Hello from Callable"; } }
Main.java
public class Main { public static void main(String[] args) throws Exception { TaskRunnable objTaskRunnable = new TaskRunnable(); //Instance of Runnable TaskCallable objTaskCallable = new TaskCallable(); //Instance of Callable String strText = objTaskCallable.call(); System.out.println(strText); //I am a Blocking Operation objTaskRunnable.run(); } }
Output
Hello from TaskCallable, executed by main thread Hello from Callable Hello from TaskRunnable, executed by main thread
Now the same code can be executed using ExecutionService Framework by taking thread from thread pool as below
Main.java
public class Main { public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(3); // Pool of 3 threads// Submit tasks to the executor TaskCallable objTaskCallable = new TaskCallable(); TaskRunnable objTaskRunnable = new TaskRunnable(); Future<String> strText = executorService.submit(objTaskCallable); System.out.println(strText.get()); //I am a Blocking Operation executorService.submit(objTaskRunnable); executorService.shutdown(); } }
Output
Hello from TaskCallable, executed by pool-1-thread-1 thread Hello from Callable Hello from TaskRunnable, executed by pool-1-thread-2 thread