What is Java Threads?
In Java, a thread is the smallest part of a program that can run independently. It is called a lightweight subprocess because it runs inside a larger program but performs its own task at the same time.
Threads make it possible for Java applications to handle multiple activities at once, for example, downloading a file while updating the user interface.
Java has strong support for multithreading through two main tools:
- The java.lang.Thread class, which helps you create and manage threads directly.
- The java.util.concurrent package, which offers advanced tools for managing multiple threads efficiently.
Every Java program starts with a main thread, which begins the program’s execution. From that main thread, you can create additional threads to perform different tasks at the same time.
For example, imagine you use a music player app, one thread can play songs, another can download new songs, and another can update the progress bar.
Advantages of Using Threads in Java
1) Concurrent Execution (Multitasking): Threads allow multiple tasks to run at the same time. This means your program doesn’t have to wait for one task to finish before starting another.
2) Better CPU Utilization: Threads can run in parallel, making full use of CPU power. This leads to faster and more efficient performance.
3) Improved Responsiveness: In applications with a graphical user interface (GUI), threads keep the program active and responsive.
4) Simpler Program Structure: Threads help in writing cleaner and more organized code for tasks that happen repeatedly or simultaneously
Creating Threads in Java
Java provides two primary ways to create a thread:
- Extending the Thread Class
- Implementing the Runnable Interface
1) Extending the Thread Class
In this approach, you create your own class that extends the built-in Thread class. Inside this class. you override the run() method, which defines the task that will execute when the thread starts.
Example:
// Custom thread class extending Thread
class MessageThread extends Thread {
@Override
public void run() {
System.out.println("Thread started: Displaying message...");
for (int i = 1; i <= 3; i++) {
System.out.println("Message " + i);
try {
Thread.sleep(500); // pause for 0.5 seconds
} catch (InterruptedException e) {
System.out.println("Thread interrupted!");
}
}
System.out.println("Thread finished successfully!");
}
}
public class ThreadExample1 {
public static void main(String[] args) {
MessageThread t1 = new MessageThread(); // create thread object
t1.start(); // start thread execution
}
}
Output:
Thread started: Displaying message...
Message 1
Message 2
Message 3
Thread finished successfully!
Explanation:
- The start() method is used to begin the thread.
- Internally, it calls the run() method in a separate path of execution.
2) Implementing the Runnable Interface
The second and more flexible way to create threads is by implementing the Runnable interface. This approach is preferred in real-world applications because it allows your class to extend another class if needed.
Example:
// Class implementing Runnable
class TaskPrinter implements Runnable {
@Override
public void run() {
System.out.println("Runnable thread started...");
for (int i = 1; i <= 5; i++) {
System.out.println("Printing task number: " + i);
try {
Thread.sleep(400); // delay for 0.4 seconds
} catch (InterruptedException e) {
System.out.println("Runnable thread interrupted!");
}
}
System.out.println("Runnable thread completed!");
}
}
public class ThreadExample2 {
public static void main(String[] args) {
TaskPrinter task = new TaskPrinter(); // create runnable object
Thread thread = new Thread(task); // pass runnable to thread
thread.start(); // start thread execution
}
}
Output:
Runnable thread started...
Printing task number: 1
Printing task number: 2
Printing task number: 3
Printing task number: 4
Printing task number: 5
Runnable thread completed!
Thread Lifecycle
A thread in Java doesn’t start running immediately after creation. It first goes through several states like New, Runnable, Running, Waiting/Blocked, and Terminated, depending on how it is scheduled and executed by the JVM.
1) New: This is the initial state of a thread. The thread is created using the new keyword, but has not started yet. It exists in memory but is not active.
Example:
Thread t = new Thread();
- Here, the thread t is in the New state.
2) Runnable: When we call the start() method, the thread moves to the Runnable state. It means the thread is ready to execute and is waiting for the CPU to assign processing time.
The actual execution depends on the thread scheduler, which decides when the thread will run.
Example:
t.start(); // Thread is now in Runnable state
3) Running: When the CPU picks the thread from the runnable pool, it enters the Running state. In this state, the thread’s run() method starts executing. The thread performs its assigned task until it either finishes or gets paused.
Example:
public void run() {
System.out.println("Thread is running...");
}
4) Blocked/Waiting: Thread is waiting for resources or another thread.
A thread enters this state when it is paused temporarily.
This can happen when:
- It’s waiting for another thread to complete a task (join()).
- It’s waiting for a resource (like file access or network I/O).
- It’s put to sleep intentionally (Thread.sleep()).
- It’s waiting for notification (wait() / notify() mechanism).
Example:
try {
Thread.sleep(1000); // Thread is in waiting state for 1 second
} catch (InterruptedException e) {
e.printStackTrace();
}
5) Terminated: Once the thread finishes its task, it enters the Terminated or Dead state. The thread can no longer be restarted.
Example:
System.out.println("Thread execution completed!");
Example of Thread Lifecycle Methods:
// Demonstration of Java Thread Lifecycle Methods
class MyWorker extends Thread {
@Override
public void run() {
System.out.println("Step 1: Worker thread started running...");
try {
// Simulate some work with sleep()
System.out.println("Step 2: Worker thread going to sleep for 1 second...");
Thread.sleep(1000); // Thread goes into Waiting state
}
catch (InterruptedException e) {
System.out.println("Worker thread was interrupted unexpectedly!");
}
System.out.println("Step 3: Worker thread finished its job.");
}
}
public class ThreadLifecycleExample {
public static void main(String[] args) throws InterruptedException {
System.out.println("Main thread: Creating a worker thread...");
// Creating a new thread
MyWorker worker = new MyWorker();
System.out.println("Main thread: Starting the worker thread...");
worker.start(); // Moves thread from New → Runnable → Running
// join() makes the main thread wait until the worker thread finishes
System.out.println("Main thread: Waiting for worker to finish...");
worker.join(); // Main thread enters Waiting state
System.out.println("Main thread: Worker finished! Program ends.");
}
}
Output:
Main thread: Creating a worker thread...
Main thread: Starting the worker thread...
Main thread: Waiting for worker to finish...
Step 1: Worker thread started running...
Step 2: Worker thread going to sleep for 1 second...
Step 3: Worker thread finished its job.
Main thread: Worker finished! Program ends.
Synchronization in Threads
When multiple threads work together in a program, they sometimes share the same resource, like a file, variable, or printer.
Synchronization in Java is a way to control access to shared resources. It helps ensure that one thread completes its operation before another starts working on the same resource.
It’s like having a lock on a room; only one person (thread) can enter at a time. When the person leaves (the thread finishes), the lock is released, and another can enter.
Using the synchronized Keyword
You can use synchronized in two ways:
- Synchronized Blocks – only a specific part of the code is locked.
- Synchronized Methods – entire method is locked.
Example:
// Demonstration of Thread Synchronization in Java
class Printer {
// synchronized ensures one thread at a time uses this method
synchronized void printDocument(String docName) {
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + " is printing: " + docName + " [Page " + i + "]");
try {
Thread.sleep(400); // simulate printing delay
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted.");
}
}
}
}
// Thread class representing a user trying to print a document
class UserThread extends Thread {
private final Printer printer;
private final String document;
UserThread(Printer printer, String document) {
this.printer = printer;
this.document = document;
}
@Override
public void run() {
printer.printDocument(document);
}
}
public class SyncExample {
public static void main(String[] args) {
Printer sharedPrinter = new Printer();
// Two users trying to print at the same time
UserThread user1 = new UserThread(sharedPrinter, "Resume.pdf");
UserThread user2 = new UserThread(sharedPrinter, "ProjectReport.docx");
user1.setName("User-1");
user2.setName("User-2");
user1.start();
user2.start();
}
}
Output:
User-1 is printing: Resume.pdf [Page 1]
User-1 is printing: Resume.pdf [Page 2]
User-1 is printing: Resume.pdf [Page 3]
User-2 is printing: ProjectReport.docx [Page 1]
User-2 is printing: ProjectReport.docx [Page 2]
User-2 is printing: ProjectReport.docx [Page 3]
Also Learn Important Topics of Java
- What are the operators in Java Programming?
- What are Data Types in Java?
- What are the Variables in Java?
- Learn Java syntax.
- What is Java if else statements?
- What is method overloading in Java?
- What are the Classes and Objects in Java?
- What is Polymorphism in Java?
- What is Interfaces in Java?

M.Sc. (Information Technology). I explain AI, AGI, Programming and future technologies in simple language. Founder of BoxOfLearn.com.