1. Thread Constructor(new Thread()) takes runnable as argument but not callable
  2. 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.
  3. Managing Thread lifecycle is difficult hence ExecutionService framework was introduced
  4. 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.

  1. Use of Thread Pool reduces response time by avoiding thread creation during request or task processing.
  2. 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.
  3. Thread Pool in Java application increases stability of system by creating a configured number of threads decided based on system load and available resource.
  4. 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

Comments are closed.