0% found this document useful (0 votes)
4 views22 pages

Multi Threading & Executor Service: 12.1) What Is A Thread?

The document provides an overview of multi-threading and the Executor Service in Java, explaining the concept of threads, their creation, communication, and interaction with the CPU. It details how to create threads by extending the Thread class or implementing the Runnable interface, as well as the various states of a thread and its priority levels. Additionally, it highlights the benefits of multi-threading, such as improved performance and responsiveness in applications.

Uploaded by

Darbar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views22 pages

Multi Threading & Executor Service: 12.1) What Is A Thread?

The document provides an overview of multi-threading and the Executor Service in Java, explaining the concept of threads, their creation, communication, and interaction with the CPU. It details how to create threads by extending the Thread class or implementing the Runnable interface, as well as the various states of a thread and its priority levels. Additionally, it highlights the benefits of multi-threading, such as improved performance and responsiveness in applications.

Uploaded by

Darbar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 22

12.

Multi threading & Executor Service


12.1) What is a Thread?
A Thread is a small part of a program that can execute concurrently with other parts of the program.
It allows a program to perform multiple tasks simultaneously, such as handling multiple user requests
or executing different tasks at the same time.
Threads can be created by either:
 Extending the Thread class, or by
 Implementing the Runnable interface.
Use threads when you need to perform independent tasks concurrently, such as managing multiple
requests or executing large, time-consuming jobs.
Thread Communication:
Threads can communicate with each other to coordinate their work using methods like wait(), notify(),
and notifyAll().
 wait(): Causes the current thread to release the monitor and go to the waiting state.
 notify(): Wakes up a single thread that is waiting on the monitor.
 notifyAll(): Wakes up all threads that are waiting on the monitor.
Thread and CPU Interaction:
Threads have a direct relationship with the CPU. Each thread runs on a CPU core, and if you have
more threads than CPU cores, some threads may enter a waiting state until a core becomes available.
Single-threaded Program:
In a single-threaded program (or single-core system), all tasks run sequentially, with only one task
being processed at a time.
The main thread, created by the Java Virtual Machine (JVM), executes the program.
Dual-Core CPUs and Beyond:
Dual-core processors (released by Intel in 2005) allow for parallel execution of multiple threads,
improving performance by utilizing both cores.
Multi-threading vs. Multi-programming:
 Multi-threading: Involves multiple threads running within the same program.
 Multi-programming: Involves multiple independent programs running on the system, often
sharing system resources.
Thread Behavior on Multiple Cores:
If there are 4 threads in your program and your system has 4 cores, the threads can run
simultaneously across the cores.
If the number of threads exceeds the number of available CPU cores, some threads will remain in a
waiting state until a core becomes available.
public class Main {
public static void main(String[] args) {
long start_time = System.currentTimeMillis();
// task-1
for (int i = 1; i <= 100; i++) {
System.out.printf("%d* ", i);
}
System.out.println("\nCompleted * task");
// task-2
for (int i = 1; i <= 100; i++) {
System.out.printf("%d$ ", i);
}
System.out.println("\nCompleted $ task");
// task-3
for (int i = 1; i <= 100; i++) {
System.out.printf("%d# ", i);
}
System.out.println("\nCompleted # task");
long end_time = System.currentTimeMillis();
System.out.println("Total Time taken(in ms) is: " + (end_time - start_time));
}
}

// 1* 2* 3* 4* 5* 6* 7* 8* 9* 10* 11* 12* 13* 14* 15* 16* 17* 18* 19* 20* 21* 22* 23* 24* 25*
26* 27* 28* 29* 30* 31* 32* 33* 34* 35* 36* 37* 38* 39* 40* 41* 42* 43* 44* 45* 46* 47* 48*
49* 50* 51* 52* 53* 54* 55* 56* 57* 58* 59* 60* 61* 62* 63* 64* 65* 66* 67* 68* 69* 70* 71*
72* 73* 74* 75* 76* 77* 78* 79* 80* 81* 82* 83* 84* 85* 86* 87* 88* 89* 90* 91* 92* 93* 94*
95* 96* 97* 98* 99* 100*
// Completed * task
//
1$ 2$ 3$ 4$ 5$ 6$ 7$ 8$ 9$ 10$ 11$ 12$ 13$ 14$ 15$ 16$ 17$ 18$ 19$ 20$ 21$ 22$ 23$ 24$ 25$ 26$

27$ 28$ 29$ 30$ 31$ 32$ 33$ 34$ 35$ 36$ 37$ 38$ 39$ 40$ 41$ 42$ 43$ 44$ 45$ 46$ 47$ 48$ 49$ 50
$ 51$ 52$ 53$ 54$ 55$ 56$ 57$ 58$ 59$ 60$ 61$ 62$ 63$ 64$ 65$ 66$ 67$ 68$ 69$ 70$ 71$ 72$ 73$
74$ 75$ 76$ 77$ 78$ 79$ 80$ 81$ 82$ 83$ 84$ 85$ 86$ 87$ 88$ 89$ 90$ 91$ 92$ 93$ 94$ 95$ 96$ 97
$ 98$ 99$ 100$
// Completed $ task
// 1# 2# 3# 4# 5# 6# 7# 8# 9# 10# 11# 12# 13# 14# 15# 16# 17# 18# 19# 20# 21# 22# 23# 24# 25#
26# 27# 28# 29# 30# 31# 32# 33# 34# 35# 36# 37# 38# 39# 40# 41# 42# 43# 44# 45# 46# 47# 48#
49# 50# 51# 52# 53# 54# 55# 56# 57# 58# 59# 60# 61# 62# 63# 64# 65# 66# 67# 68# 69# 70# 71#
72# 73# 74# 75# 76# 77# 78# 79# 80# 81# 82# 83# 84# 85# 86# 87# 88# 89# 90# 91# 92# 93# 94#
95# 96# 97# 98# 99# 100#
// Completed # task
// Total Time taken(in ms) is: 57

Need of multi threading


1. Tasks are independent of each other.
2. Multi core CPU is sitting idle most of the time.
3. Big tasks can be divided into smaller parts.
4. To make responsive code.

12.2) Creating a Thread


1) By extending Thread Class
 We can’t estimate which thread completes its execution first or which thread starts first. It's
purely based on your computer's thread scheduler and the CPU’s workload.
 No.of classes created is not equal to no.of threads.
 The number of instances is equal to the number of threads only if the class extends Thread or
implements Runnable, and you explicitly call start() on each instance.
 For example, if you create three instances of such a class and call start() on each, then three
separate threads will be created and run concurrently.
 But if you simply create 3 instances of a regular class that doesn’t extend Thread or implement
Runnable, then you won’t have 3 threads - everything will run on the main thread.
 In the below example 3 threads are created and started and then executed independently.
public class Task extends Thread {
private final char symbol;

Task(char symbol) {
this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.printf("%d%c ", i, symbol);
}
System.out.println("\n" + Thread.currentThread().getName() + " : Completed " + symbol
+ " task");
}
}

class Main {
public static void main(String[] args) {
Task t1 = new Task('*');
Task t2 = new Task('$');
Task t3 = new Task('#');
long start_time = System.currentTimeMillis();
t1.start();
t2.start();
t3.start();
long end_time = System.currentTimeMillis();
System.out.println("Total Time taken(in ms) is: " + (end_time - start_time));
System.out.printf("%s : completed", Thread.currentThread().getName());
}
}

// 1# 2# 3# 4# 5# 6# 7# 8# 9# 10# 11# 12# 13# 14# 15# 16# 17# 18# 19# 20# 21#
// 22# 23# 24# 25# 26# 27# 28# 29# 30# 31# 32# 33# 34# 35# 36# 37# 38# 39# 40#
// 41# 42# 43# 44# 45# 46# 47# 1$ 2$ 3$ 4$ 5$ 6$ 7$ 8$ 9$ 10$ 11$ 12$ 13$ 14$
// 15$ 16$ 17$ 18$ 19$ 20$ 21$ 22$ 23$ 24$ 25$ 26$ 27$ 28$ 29$ 30$ 31$ 32$ 33$
// 34$ 35$ 36$ 37$ 38$ 39$ 40$ 41$ 42$ 43$ 44$ 45$ 46$ 47$ 48$ 49$ 50$ 51$ 52$
// 53$ 54$ 55$ 56$ 57$ 58$ 59$ 60$ 61$ 62$ 63$ 64$ 65$ 66$ 67$ 68$ 69$ 70$ 71$
// 72$ 73$ 74$ 75$ 76$ 77$ 78$ 79$ 80$ 81$ 82$ 83$ 84$ 85$ 86$ 87$ 88$ 89$ 90$
// 91$ 92$ 93$ 94$ 95$ 96$ 97$ 98$ 99$ 100$ 1* 2* 3* 4* 5* 6* 7* 8* 9* 10* 11*
// 12* 13* 14* 15* 16* 17* 18* 19* 20* 21* 22* 23* 24* 25* 26* 27* 28* 29* 30*
// 31* 32* 33* 34* 35* 36* 37* 38* 39* 40* 41* 42* 43* 44* 45* 46* 47* 48* 49*
// 50* 51* 52* 53* 54* 55* 56* 57* 58* 59* 60* 61* 62* 63* 64* 65* 66* 67* 68*
// 69* 70* 71* 72* 73* 74* 75* 76* 77* 78* 79* 80* 81* 82* 83* 84* Total Time
// taken(in ms) is: 0
// 48# 49# 50# 51# 52# 53# 54# 55# 56# 57# 58# 59# 60# 61# 62# 63# 64# 65# 66#
// 67# 68# 69# 70#
// Thread-1 : Completed $ task
// 85* 86* 87* 88* 89* 90* 91* 92* 93* 94* 95* 96* 97* 98* 99* 100* main :
// completed71#
// Thread-0 : Completed * task
// 72# 73# 74# 75# 76# 77# 78# 79# 80# 81# 82# 83# 84# 85# 86# 87# 88# 89# 90#
// 91# 92# 93# 94# 95# 96# 97# 98# 99# 100#
// Thread-2 : Completed # task

2) By implementing Runnable Interface


public class InterfaceDemo implements Runnable {
private final char symbol;

public InterfaceDemo(char symbol) {


this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.printf("%d%c ", i, symbol);
}
System.out.println(Thread.currentThread().getName() + " executed succesfully");
}
}

public class MainInterface {


public static void main(String[] args) {
InterfaceDemo d1 = new InterfaceDemo('%');
InterfaceDemo d2 = new InterfaceDemo('^');
InterfaceDemo d3 = new InterfaceDemo('(');
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
t1.start();
t2.start();
new Thread(d3).start();
System.out.printf("%s executed succesfully.", Thread.currentThread().getName());
}
}

12.3) States of a Thread


Thread State Description

A thread is created but not yet started. It has been initialized but start()
New
has not been called.

The thread is ready for execution. The start() method has been called, and
Runnable
it is waiting to be scheduled by the JVM.

The thread is actively executing its tasks. The JVM is actively scheduling
Running
the thread to run.

The thread is alive but not executing. It may be waiting for resources,
Blocked/Sleeping/Waiting
locks, or other conditions to be met.

The thread has finished execution or was stopped. It no longer runs and
Terminated
its lifecycle has ended.

12.4) Thread Priority


 Threads have priority levels form 1(lowest) to 10(highest), with a default value of 5.
 Thread priority just suggests the importance of a thread to the JVM’s sheduler but doesn’t
guarantee the order of execution.
 Thread.setPriority(), Thread.getPriority() are the methods used to get and set the priority for a
thread.
public class InterfaceDemo implements Runnable {
private final char symbol;

public InterfaceDemo(char symbol) {


this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.printf("%d%c ", i, symbol);
}
System.out.println(Thread.currentThread().getName() + " executed succesfully");
}
}

public class MainInterface {


public static void main(String[] args) {
long start_time = System.currentTimeMillis();
InterfaceDemo d1 = new InterfaceDemo('%');
InterfaceDemo d2 = new InterfaceDemo('^');
InterfaceDemo d3 = new InterfaceDemo('(');
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
Thread t3 = new Thread(d3);
t1.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.setPriority(Thread.MAX_PRIORITY);
t2.start();
t3.setPriority(Thread.NORM_PRIORITY);
t3.start();
long end_time = System.currentTimeMillis();
System.out.printf("%s executed succesfully in %d ms.",
Thread.currentThread().getName(),
(end_time - start_time));
}
}

12.5) Join Method


1. Join method allows one thread to wait for the completion of another .
2. Join helps in synchronizing multiple threads, ensures that a thread completes its execution
before the next steps in the calling thread proceed.
3. If t is a thread object whose thread is currently executing, t.join(); causes current thread to
pause its execution until t’s thread terminates.
4. Join allows overloads to allow the programmer to specify the waiting period.
 join(long millis) -> waits for the thread to die for the specified no.of milliseconds.
 join(long millis, int nanos) -> waits for the thread to die for the specified no.of (milliseconds
+ nanoseconds).
5. The join() method in Java throws a checked exception called InterruptedException.
6. Since it's a checked exception, it must be handled either using a try-catch block or by declaring
throws InterruptedException in the method signature.
public class InterfaceDemo implements Runnable {
private final char symbol;

public InterfaceDemo(char symbol) {


this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.printf("%d%c ", i, symbol);
}
System.out.println(Thread.currentThread().getName() + " executed succesfully");
}
}

public class JoinDemo {


public static void main(String[] args) throws InterruptedException {
InterfaceDemo d1 = new InterfaceDemo('A');
InterfaceDemo d2 = new InterfaceDemo('B');
InterfaceDemo d3 = new InterfaceDemo('C');
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
Thread t3 = new Thread(d3);
t1.start();
t2.start();
t2.join();
t3.start();
}
}

t1.start() → Starts thread t1. It runs concurrently with other threads.


t2.start() → Starts thread t2, which also runs concurrently.
t2.join() → The main thread pauses and waits for t2 to finish. This does not stop t1 - it continues
executing independently.
t3.start() → Starts only after t2 has completed, because the main thread was waiting on t2.join().

12.6) Synchronize Keyword

 The synchronized keyword in Java ensures that only one thread can execute a block of code at a
time, providing mutual exclusion and preventing race conditions.
 When a thread enters a synchronized block or method, it acquires a lock on the object (for
instance methods) or on the class (for static methods).
 Changes made by threads inside a synchronized block are visible to all other threads.
1. Instance method → locks the object
2. Static method → locks the class
public class Counter {
private int count = 0;

public void increment() {


count++;
}
public int getCount() {
return count;
}
}
public class SynchronizedThread extends Thread {
Counter count;

SynchronizedThread(Counter count) {
this.count = count;
}
@Override
public void run() {
for (int i = 1; i <= 100000; i++) {
count.increment();
}
}
}

public class SynchronizedDemo {


public static void main(String[] args) {
long start_time = System.currentTimeMillis();
Counter c = new Counter();
SynchronizedThread t1 = new SynchronizedThread(c);
SynchronizedThread t2 = new SynchronizedThread(c);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
System.out.println("Interrupted exception occured: " + e.getMessage());
}

long end_time = System.currentTimeMillis();


System.out.printf("Total count: %d in %d ms", c.getCount(), (end_time - start_time));
}
}

// Total count: 130823 in 8 ms

If we make the increment method synchronized then,


public class Counter {
private int count = 0;

public synchronized void increment() {


count++;
}
public int getCount() {
return count;
}
}

// Total count: 200000 in 20 ms


12.7) Thread Communication

1. sleep(long millis):
sleep should not be called on a specific thread object because it is a static method of the Thread
class. This means that Thread.sleep(milliseconds) pauses the currently executing thread,
regardless of which thread object it is called on. If we write Thread.sleep(timeInMilliseconds);
inside the main method, the main thread will transition from the running state to the timed
waiting state, and then back to the running state after the specified duration and sleep() need to
handle the InterruptedException.
2. yield():
Causes the currently executing threads to pause and allow other threads to execute. It's a way of
suggesting that other threads of the same priority can run.
3. wait():
Causes the current thread to wait until another thread invokes the notify() or notifyAll() method
for this object. It releases the lock held by this thread.
4. notify():
Wakes up a single thread that is waiting on the objects monitor. If any threads are waiting, one is
chosen to be awakened.
5. notifyAll():
Wakes up all the threads that are waiting on the object’s monitor.
CHALLENGES
1) Write a program that creates 2 threads. Each thread should print “Hello form Thread X”, where X
is the no.of the thread(1 or 2), 10 times then terminate.
public class HelloThread extends Thread {
private final int threadNo;

public HelloThread(int threadNo) {


this.threadNo = threadNo;
}
public int getThreadNo() {
return threadNo;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.printf("(%d)Hello form Thread-%d\n", (i + 1), threadNo);
}
}
}

public class Challenge1 {


public static void main(String[] args) {
HelloThread t1 = new HelloThread(1);
HelloThread t2 = new HelloThread(2);
t1.start();
t2.start();
}
}

If we call t1.run() instead of t1.start(), the run() method will be executed just like a normal method
call, and it will not start a new thread. As a result, the code inside run() will execute in the current
thread, which is typically the main thread. So Thread.currentThread().getName() will return main.
// (1)Hello form Thread-2
// (2)Hello form Thread-2
// (3)Hello form Thread-2
// (4)Hello form Thread-2
// (5)Hello form Thread-2
// (6)Hello form Thread-2
// (1)Hello form Thread-1
// (2)Hello form Thread-1
// (3)Hello form Thread-1
// (4)Hello form Thread-1
// (5)Hello form Thread-1
// (6)Hello form Thread-1
// (7)Hello form Thread-1
// (8)Hello form Thread-1
// (9)Hello form Thread-1
// (10)Hello form Thread-1
// (7)Hello form Thread-2
// (8)Hello form Thread-2
// (9)Hello form Thread-2
// (10)Hello form Thread-2
2) Write a program that starts a thread and prints its state after each significant event (creation,
starting and termination). use Thread.sleep() to simulate long-running tasks and
Thread.getState() to print the thread’s state.
public class PrintOdd extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i += 2) {
System.out.print(i + " ");
}
}
}

public class Challenge2 {


public static void main(String[] args) throws InterruptedException {
PrintOdd p1 = new PrintOdd();
System.out.println(p1.getState());
p1.start();
System.out.println(p1.getState());
p1.join();
System.out.println(p1.getState());
}
}

// NEW
// RUNNABLE
// 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53
// 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99
// TERMINATED

Note:
The run() method (used in threads) is not allowed to say it throws a checked exception like
InterruptedException. This is because the original run() method in the Runnable interface doesn't
throw any checked exceptions.
So, if you use Thread.sleep() inside the run() method — which can throw InterruptedException — you
must catch that exception using a try-catch block.
public void run() throws InterruptedException { // ❌ Not allowed
Thread.sleep(1000);
}

public void run() {


try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e); // Wrap it in an unchecked exception if needed
}
}

So basically:
 run() can’t use throws InterruptedException.
 You have to handle the exception inside the method using try-catch.
 If you want to stop or report the error, you can wrap it in a RuntimeException and throw that.

3) Create 3 threads. Ensure that 2nd thread starts only after the 1st thread ends and the 3rd starts
only after the 2nd thread ends using the join method. Each thread should print its start and end
along with its name.
public class Symbols extends Thread {
private final char symbol;

public Symbols(char symbol) {


this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
@Override
public void run() {
System.out.printf("%s is started", symbol);
for (int i = 1; i <= 100; i++) {
System.out.print(symbol + " ");
}
System.out.printf("Thread %c is ended", symbol);
}
}

public class Challenge3 {


public static void main(String[] args) throws InterruptedException {
Symbols s1 = new Symbols('0');
Symbols s2 = new Symbols('1');
Symbols s3 = new Symbols('3');
System.out.println(s1.getName() + " is getting invoked by start method");
s1.start();
s1.join();
System.out.println(s2.getName() + " is getting invoked by start method");
s2.start();
s2.join();
System.out.println(s3.getName() + " is getting invoked by start method");
s3.start();

}
}

// Thread-0 is getting invoked by start method


// 0 is started0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Thread 0 is
// endedThread-1 is getting invoked by start method
// 1 is started1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
// 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
// 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 Thread 1 is
// endedThread-2 is getting invoked by start method
// 3 is started3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
// 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
// 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 Thread 3 is ended
4) Simulate a traffic signal using threads. Create 3 threads representing three signals: RED, YELLOW,
GREEN. Each signal should be on for a certain time, then switch to the next signal in order. Use
sleep for timing and synchronize to make sure only one signal is active at a time.
public enum TrafficEnum {
RED("Stop"), YELLOW("turn on the engine"), GREEN("You can goooooo");

final String action;


TrafficEnum(String action) {
this.action = action;
}
public String getAction() {
return action;
}
}

public class TrafficThread extends Thread {


String light;

TrafficThread(String light) {
this.light = light;
}
@Override
public void run() {
System.out.println(TrafficEnum.valueOf(light).getAction());
}
}

public class Challenge4 {


public static void main(String[] args) throws InterruptedException {
TrafficThread t1 = new TrafficThread("RED");
TrafficThread t2 = new TrafficThread("GREEN");
TrafficThread t3 = new TrafficThread("YELLOW");
t1.start();
Thread.sleep(10000);
t3.start();
Thread.sleep(5000);
t2.start();

}
}

// Stop
// turn on the engine
// You can goooooo

12.8) Intro to Executor Service


 ExecutorService is an interface in the Java Concurrency API that provides a powerful framework
for managing and executing tasks asynchronously, without the need to manually handle thread
creation and management.
 Instead of creating a new thread for each task, ExecutorService maintains a pool of reusable
threads, which significantly improves performance — particularly when executing many short-
lived tasks.
 This approach reduces the overhead associated with thread creation and destruction and allows
for better control over resource usage.
 In simple terms, the Java Concurrency API enables multithreading, and ExecutorService is one of
its core tools for handling tasks in a clean, efficient, and scalable way.
 In Java, threads managed by an ExecutorService do not die immediately after completing a task.
Instead, they remain alive in a thread pool and wait to execute more tasks — until the executor
is explicitly shut down.
 It's similar to a waiter in a restaurant:
We don’t fire the waiter after serving a single customer — they continue serving more
customers as they arrive.
 Likewise, in ExecutorService, threads are reused to handle multiple tasks efficiently, rather than
being destroyed and recreated for each one.
 This thread reuse significantly reduces performance overhead and provides better resource
management compared to manually creating and starting a new thread for every task.
Demo of newSingleThreadExecutor() is shown below which executes only one thread at a time..
public class Symbols extends Thread {
private final char symbol;

public Symbols(char symbol) {


this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
@Override
public void run() {
System.out.printf("\n%s with the symbol:%s is started\n",
Thread.currentThread().getName(), symbol);
for (int i = 1; i <= 100; i++) {
System.out.print(symbol + " ");
}
System.out.printf("\n%s with the symbol:%s is ended\n",
Thread.currentThread().getName(), symbol);
}
}

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

public class Executor {


public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
Symbols s1 = new Symbols('$');
Symbols s2 = new Symbols('%');
service.submit(s1);
service.submit(s2);
service.shutdown();
}
}

// pool-1-thread-1 with the symbol:$ is started


// $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $
// $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $
// $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $
// pool-1-thread-1 with the symbol:$ is ended
// pool-1-thread-1 with the symbol:% is started
// % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % %
// % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % %
// % % % % % % % % % % % % % % % % % % % % % %
// pool-1-thread-1 with the symbol:% is ended

12.9) Multiple Threads with executor


Let us use multiple threads. In the below example we have used 2 threads to complete 3 tasks.
Thread 2 had done the 2 tasks that are 1st and 3rd , and Thread1 had only completed 2nd task.
public class Symbols extends Thread {
private final char symbol;

public Symbols(char symbol) {


this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
@Override
public void run() {
System.out.printf("\n%s with the symbol:%s is started\n",
Thread.currentThread().getName(), symbol);
for (int i = 1; i <= 100; i++) {
System.out.print(symbol + " ");
}
System.out.printf("\n%s with the symbol:%s is ended\n",
Thread.currentThread().getName(), symbol);
}
}

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

public class Executor {


public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
Symbols s1 = new Symbols('$');
Symbols s2 = new Symbols('%');
Symbols s3 = new Symbols('=');
service.submit(s1);
service.submit(s2);
service.submit(s3);
service.shutdown();
}
}
// pool-1-thread-2 with the symbol:% is started
// pool-1-thread-1 with the symbol:$ is started
// $ $ $ % % % % % % % % % $ % % % % % % % % % % % % % % % % % % % % % % % $ $ $
// $ $ % $ % $ $ $ % % % $ % % % $ % % $ $ $ $ $ % % % % % % % % % $ % % % % $ %
// % % $ % $ $ % $ $ $ $ % % % % % $ $ $ % $ % $ $ % % % % % % % % % % $ $ $ $ $
// $ $ $ % % % % % % $ % % $ $ $ $ % $ $ % $ % % % % % % $ $ $ % % % $ % $ $ $ $
// % % % $ $
// pool-1-thread-2 with the symbol:% is ended
// $ $ $ $ $ $ $ $
// pool-1-thread-2 with the symbol:= is started
// = = $ $ $ $ = = $ = = = = = = = = $ $ = = $ $ $ $ = = = = $ $ $ $ $ $ $ $ $ $
// $ $ $ $ $ $ = = $ = = = = = = = = = = = = = = = $ $ = = $ =
// pool-1-thread-1 with the symbol:$ is ended
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// = = = = = = = = = = = = = = = = = = = = = = =
// pool-1-thread-2 with the symbol:= is ended

And lets see an example where we shutdown the executor within 10 seconds regardless of weather
executor fully executed or not.
public class Symbols extends Thread {
private final char symbol;

public Symbols(char symbol) {


this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
@Override
public void run() {
System.out.printf("\n%s with the symbol:%s is started\n",
Thread.currentThread().getName(), symbol);
for (int i = 1; i <= 100; i++) {
System.out.print(symbol + " ");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.printf("\n%s with the symbol:%s is ended\n",
Thread.currentThread().getName(), symbol);
}
}

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

public class Executor {


public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(2);
Symbols s1 = new Symbols('$');
Symbols s2 = new Symbols('%');
Symbols s3 = new Symbols('=');
service.submit(s1);
service.submit(s2);
service.submit(s3);
service.shutdown();
if (service.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("Completed under 10 seconds");
} else {
System.out.println("Haven't completed under 10 seconds");
service.shutdownNow();
}
}
}

// pool-1-thread-1 with the symbol:$ is started

// pool-1-thread-2 with the symbol:% is started


// $ % $ % $ % % $ $ % $ % % $ % $ $ % % $ $ % % $ $ % $ % $ % % $ $ % % $ $ % %
// $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $
// % $ %
// $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $
// % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ %
// $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % $ % Haven't completed under 10
// seconds
// $

12.10) Returning Futures


 In Java, we can use the Callable interface (instead of Runnable) when we want a thread to return
a result or throw a checked exception.
 The result of a Callable task is wrapped in a Future object.
 The Future represents the result of an asynchronous computation — which may or may not be
available yet.
 You submit a Callable to an ExecutorService using the submit() method, which returns a Future.
 You can then use future.get() to retrieve the result once the task is complete.
Key Difference:
 Runnable does not return a value.
 Callable<T> returns a value of type T, which can be accessed using a Future<T>.
import java.util.concurrent.Callable;

public class Name implements Callable<String> {


private final String name;
public Name(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
System.out.printf("Getting %s.......\n", name);
Thread.sleep(4000);
return name + " was returned";
}
}

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService service = Executors.newFixedThreadPool(2);
Name task1 = new Name("hemanth");
Name task2 = new Name("Java");
Name task3 = new Name("KG Coding");
Future<String> name1 = service.submit(task1);
Future<String> name2 = service.submit(task2);
Future<String> name3 = service.submit(task3);
System.out.println("\n" + name1.get());
System.out.println("\n" + name2.get());
System.out.println("\n" + name3.get());
service.shutdown();
}
}

// Getting Java.......Getting hemanth.......hemanth was returned


// Java was returned
// Getting KG Coding.......KG Coding was returned

CHALLENGES
1) Write a program that creates a single-threaded executor service. Define and submit a simple
Runnable task that prints numbers from 1-10. After submission, shutdown the executor.
public class Print1210 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(i);
}
}
}

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

public class Challenge1InExecutor {


public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
Print1210 task1 = new Print1210();
service.submit(task1);
service.shutdown();
}
}

// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// 10
OR
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Challenge1InExecutor {


public static void main(String[] args) {
try (ExecutorService service = Executors.newSingleThreadExecutor()) {
Print1210 task = new Print1210();
service.submit(task);
}
// we need not to explicitly shutdown the service as we are using try with
// resources.
}
}

2) Create a fixed threadpool with specified no.of threads using Executors.newFixedThreadPool(int).


submit multiple tasks to this executor, where each task should print the current threads name and
sleep for a random time between 1 and 5 seconds. Finally, shutdown the executor and handle proper
termination using awaitTermination.
public class ThreadOfChallenge2 extends Thread {
@Override
public void run() {
System.out.println("Name is: " + Thread.currentThread().getName());
try {
Thread.sleep(((int) (Math.random() * 5) + 1) * 1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

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

public class Challenge2Executor {


public static void main(String[] args) throws InterruptedException {
Scanner s = new Scanner(System.in);
System.out.print("Enter how many threads you need to complete this task: ");
int count = s.nextInt();
ExecutorService service = Executors.newFixedThreadPool(count);
ThreadOfChallenge2 task1 = new ThreadOfChallenge2();
ThreadOfChallenge2 task2 = new ThreadOfChallenge2();
ThreadOfChallenge2 task3 = new ThreadOfChallenge2();
ThreadOfChallenge2 task4 = new ThreadOfChallenge2();
ThreadOfChallenge2 task5 = new ThreadOfChallenge2();
service.submit(task1);
service.submit(task2);
service.submit(task3);
service.submit(task4);
service.submit(task5);
service.shutdown();
if (service.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("Completed in 10 seconds");
} else {
System.out.println("Haven't completed in 10 seconds");
service.shutdownNow();
}
}
}

// Enter how many threads you need to complete this task: 2


// Name is: pool-1-thread-2
// Name is: pool-1-thread-1
// Name is: pool-1-thread-1
// Name is: pool-1-thread-2
// Name is: pool-1-thread-2
// Completed in 10 seconds
OR
public class ThreadOfChallenge2 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is started");
try {
Thread.sleep(((int) (Math.random() * 5) + 1) * 1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " is ended");
}
}

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

public class Challenge2Executor {


public static void main(String[] args) throws InterruptedException {
Scanner s = new Scanner(System.in);
System.out.print("Enter how many threads you need to complete this task: ");
int count = s.nextInt();
ExecutorService service = Executors.newFixedThreadPool(count);
for (int i = 1; i <= 5; i++) {
ThreadOfChallenge2 task = new ThreadOfChallenge2();
service.submit(task);
}
service.shutdown();
if (service.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("Completed in 10 seconds");
} else {
System.out.println("Haven't completed in 10 seconds");
service.shutdownNow();
}
}
}

// Enter how many threads you need to complete this task: 3


// pool-1-thread-2 is started
// pool-1-thread-3 is started
// pool-1-thread-1 is started
// pool-1-thread-1 is ended
// pool-1-thread-2 is ended
// pool-1-thread-2 is started
// pool-1-thread-1 is started
// pool-1-thread-2 is ended
// pool-1-thread-3 is ended
// pool-1-thread-1 is ended
// Completed in 10 seconds

3) Write a program that uses an executor service to execute multiple tasks. Each task should calculate
and return the factorial of a number provided to it. Use future objects to receive the results of the
calculations. After all tasks are submitted, retrieve the results from the future, print them, and ensure
the executor service is shutdown correctly.
import java.util.concurrent.Callable;

public class Factorial implements Callable<Long> {


private final int num;
public Factorial(int num) {
this.num = num;
}
@Override
public Long call() throws Exception {
long fact = 1;
if (num == 0) {
return fact;
} else {
for (int i = 1; i <= num; i++) {
fact *= i;
}
}
return fact;
}
}

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

public class Challenge3Executor {


public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService service = Executors.newFixedThreadPool(3);
Factorial task1 = new Factorial(10);
Factorial task2 = new Factorial(9);
Factorial task3 = new Factorial(5);
Future<Long> answer1 = service.submit(task1);
Future<Long> answer2 = service.submit(task2);
Future<Long> answer3 = service.submit(task3);
System.out.println(answer1.get());
System.out.println(answer2.get());
System.out.println(answer3.get());
service.shutdown();
}
}

// 3628800
// 362880
// 120

OR
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class Challenge3Executor {


public static void main(String[] args) throws InterruptedException, ExecutionException {
List<Future<Long>> list = new ArrayList<>();
ExecutorService service = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 10; i++) {
Factorial task = new Factorial(i);
list.add(service.submit(task));
}
for (Future<Long> result : list) {
System.out.println(result.get());
}
service.shutdown();
}
}

// 1
// 2
// 6
// 24
// 120
// 720
// 5040
// 40320
// 362880
// 3628800

KEY POINTS
1. Main thread is the only user created thread that is created automatically when a program starts.
2. run() is automatically called when thread is started using the start().
3. A thread can not be in both runnable and running states.
4. join() methods causes the calling thread to stop execution until the thread it joins with, stops
running.
5. Synchronized keyword prevents 2 threads from executing a method simultaneously.
6. notify() wakes up single thread, notifyAll() wakes up all the threads.
7. Executor service must be explicitly shutdown to terminate the threads it manages.
8. Thread.yield() is a static method in Java that hints to the thread scheduler that the current
thread is willing to pause its execution temporarily, allowing other threads of the same or higher
priority a chance to execute.

You might also like