Multithreading in Java
Multithreading is a key feature in Java that enables the concurrent execution of multiple parts of a program, maximizing CPU utilization. Each independent path of execution is called a thread, which is a lightweight process.
Java provides two primary ways to create threads:
- By extending the
Threadclass - By implementing the
Runnableinterface
In this approach, you create a class that extends java.lang.Thread and override the run() method. The thread starts when you call start(), which internally invokes run() on a new thread.
Example
// Java code for thread creation by extending the Thread class
class ThreadDemo extends Thread {
public void run() {
try {
// Displaying the ID of the thread that is running
System.out.println("Thread " + Thread.currentThread().getId() + " is active");
} catch (Exception e) {
System.out.println("An exception occurred");
}
}
}
// Main Class
public class ThreadExample {
public static void main(String[] args) {
int numberOfThreads = 5;
for (int i = 0; i < numberOfThreads; i++) {
ThreadDemo threadInstance = new ThreadDemo();
threadInstance.start();
}
}
}
Sample Output (varies by system):
Thread 11 is active
Thread 13 is active
Thread 12 is active
Thread 15 is active
Thread 14 is active
In this approach, you implement the Runnable interface and define the thread task inside run(). Then you pass the Runnable object to a Thread and call start().
Example
// Java code for thread creation by implementing the Runnable interface
class ThreadRunnableDemo implements Runnable {
public void run() {
try {
System.out.println("Thread " + Thread.currentThread().getId() + " is active");
} catch (Exception e) {
System.out.println("An exception occurred");
}
}
}
// Main Class
public class RunnableExample {
public static void main(String[] args) {
int numberOfThreads = 5;
for (int i = 0; i < numberOfThreads; i++) {
Thread threadInstance = new Thread(new ThreadRunnableDemo());
threadInstance.start();
}
}
}
✅ Why Runnable is often preferred: your class can still extend another class (Java doesn’t allow multiple inheritance of classes).
A thread can exist in only one state at a time. The main states are:
- New
- Runnable
- Blocked
- Waiting
- Timed Waiting
- Terminated
State Explanations
- New: Thread object is created but
start()hasn’t been called. - Runnable: Thread is ready to run (it may actually be running or waiting for CPU).
- Blocked: Thread is waiting to acquire a monitor lock (e.g., trying to enter a
synchronizedblock). - Waiting: Thread waits indefinitely (e.g.,
join()orwait()without timeout). - Timed Waiting: Thread waits for a fixed time (e.g.,
sleep(ms)orwait(timeout)). - Terminated: Thread has finished execution.
Java provides these states via Thread.State:
NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED
Example: Demonstrating Thread States
// Java program to demonstrate thread states
class MyThread implements Runnable {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("State of thread1 after calling join() on thread2 - "
+ ThreadExample.thread1.getState());
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadExample implements Runnable {
public static Thread thread1;
public static ThreadExample obj;
public static void main(String[] args) {
obj = new ThreadExample();
thread1 = new Thread(obj);
System.out.println("State of thread1 after creating it - " + thread1.getState());
thread1.start();
System.out.println("State of thread1 after calling .start() method - " + thread1.getState());
}
public void run() {
MyThread task = new MyThread();
Thread thread2 = new Thread(task);
System.out.println("State of thread2 after creating it - " + thread2.getState());
thread2.start();
System.out.println("State of thread2 after calling .start() method - " + thread2.getState());
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("State of thread2 after calling .sleep() method - " + thread2.getState());
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("State of thread2 after it has completed execution - " + thread2.getState());
}
}
Thread priority is an integer from 1 to 10:
Thread.MIN_PRIORITY= 1Thread.NORM_PRIORITY= 5 (default)Thread.MAX_PRIORITY= 10
You can use:
getPriority()to read prioritysetPriority(int)to change priority
Note: Priority influences scheduling, but the OS/JVM scheduler may still behave differently.
Example: Getting and Setting Priorities
class PriorityExample extends Thread {
public void run() {
System.out.println("Inside run method of thread: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
PriorityExample t1 = new PriorityExample();
PriorityExample t2 = new PriorityExample();
PriorityExample t3 = new PriorityExample();
System.out.println("t1 priority: " + t1.getPriority());
System.out.println("t2 priority: " + t2.getPriority());
System.out.println("t3 priority: " + t3.getPriority());
t1.setPriority(3);
t2.setPriority(7);
t3.setPriority(10);
System.out.println("Updated t1 priority: " + t1.getPriority());
System.out.println("Updated t2 priority: " + t2.getPriority());
System.out.println("Updated t3 priority: " + t3.getPriority());
}
}
class ChildThreadExample extends Thread {
public void run() {
System.out.println("Running thread: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread.currentThread().setPriority(6);
System.out.println("Main thread priority: " + Thread.currentThread().getPriority());
ChildThreadExample t1 = new ChildThreadExample();
System.out.println("Child thread priority: " + t1.getPriority());
}
}
When the Java program starts, the first thread is the main thread.
It often:
- creates child threads
- finishes last (because it may coordinate shutdown)
Example: Controlling the Main Thread
public class MainThreadControl {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
System.out.println("Current thread: " + mainThread.getName());
mainThread.setName("PrimaryThread");
System.out.println("After name change: " + mainThread.getName());
System.out.println("Main thread priority: " + mainThread.getPriority());
mainThread.setPriority(Thread.MAX_PRIORITY);
System.out.println("Main thread new priority: " + mainThread.getPriority());
}
}
Calling join() on the current thread causes it to wait for itself → deadlock.
public class DeadlockExample {
public static void main(String[] args) {
try {
System.out.println("Entering Deadlock");
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1) Naming at Creation (Direct Method)
class CustomThread extends Thread {
CustomThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(getName() + " is running...");
}
}
public class ThreadNamingDemo {
public static void main(String[] args) {
CustomThread t1 = new CustomThread("Worker1");
CustomThread t2 = new CustomThread("Worker2");
System.out.println("Thread 1 name: " + t1.getName());
System.out.println("Thread 2 name: " + t2.getName());
t1.start();
t2.start();
}
}
2) Naming with setName() (Indirect Method)
class TaskThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " executing...");
}
}
public class ThreadRenameDemo {
public static void main(String[] args) {
TaskThread thread1 = new TaskThread();
TaskThread thread2 = new TaskThread();
System.out.println("Initial Thread 1 name: " + thread1.getName());
System.out.println("Initial Thread 2 name: " + thread2.getName());
thread1.setName("TaskRunner1");
thread2.setName("TaskRunner2");
thread1.start();
thread2.start();
}
}
Fetching the Current Thread Name
public class CurrentThreadDemo {
public static void main(String[] args) {
System.out.println("Current thread: " + Thread.currentThread().getName());
}
}
Why start() instead of calling run()?
start()creates a new thread and a new call stack- JVM then calls
run()on that new thread - Calling
run()directly runs on the same thread (no new thread created)
Example: run() vs start()
class CustomThread extends Thread {
@Override
public void run() {
System.out.println("Executing on thread ID: " + Thread.currentThread().getId());
}
}
public class RunVsStartDemo {
public static void main(String[] args) {
CustomThread t = new CustomThread();
t.run(); // runs in main thread
t.start(); // runs in a new thread
}
}
sleep() pauses the current thread temporarily.
✅ Java provides two overloads:
Thread.sleep(long millis)Thread.sleep(long millis, int nanos)
It throws InterruptedException.
Example 1: Using sleep() in Main Thread
public class MainThreadSleepExample {
public static void main(String[] args) {
try {
for (int i = 1; i <= 4; i++) {
Thread.sleep(2000);
System.out.println("Iteration: " + i);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted: " + e);
}
}
}
Example 2: Using sleep() in a Custom Thread
class CustomThreadSleepExample extends Thread {
@Override
public void run() {
try {
for (int i = 1; i <= 3; i++) {
Thread.sleep(1000);
System.out.println("Custom Thread Iteration: " + i);
}
} catch (InterruptedException e) {
System.out.println("Custom thread interrupted: " + e);
}
}
public static void main(String[] args) {
new CustomThreadSleepExample().start();
}
}
Example 3: Negative Sleep Time → IllegalArgumentException
public class NegativeSleepTimeExample {
public static void main(String[] args) {
try {
Thread.sleep(-500);
} catch (IllegalArgumentException e) {
System.out.println("Caught IllegalArgumentException: Timeout value is negative");
} catch (InterruptedException e) {
System.out.println("Interrupted: " + e);
}
}
}