Introduction
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
Creating a Thread by Extending the Thread Class
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
Creating a Thread by Implementing the Runnable Interface
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).
Thread Life Cycle (Thread States)
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 Enum Constants for Thread States
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());
}
}
Java Thread Priority in Multithreading
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());
}
}
Child Thread Inherits Parent Priority
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());
}
}
Main Thread in Java
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());
}
}
Deadlock Using Main Thread (Concept Example)
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();
}
}
}
Thread Naming in Java
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());
}
}
What Does start() Do?
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
}
}
Thread.sleep() in Java
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);
}
}
}