Working of Thread States
Threads in Java change states depending on what they’re doing. Think of it like a traffic light—sometimes they’re moving (running), sometimes waiting (blocked), and sometimes they’re done (terminated). Let’s see how these states work in real code.
1. New → Runnable (Thread Starts)
When you create a thread, it’s New. Calling start() makes it Runnable (ready to run).
Example:
Thread t = new Thread(() -> {
System.out.println("Thread is working");
});
t.start(); // Now in RUNNABLE state
New: When Thread t is created.
Runnable: After t.start(), waiting for CPU time.
2. Runnable → Running (CPU Executes Thread)
The OS picks the thread from the Runnable pool and moves it to Running.
Example:
public void run() {
System.out.println("Running!"); // Thread is now RUNNING
}
The thread is actively executing code.
3. Running → Blocked/Waiting (Paused Temporarily)
A thread can get Blocked or Waiting because of:
- sleep(): Pauses for a set time.
- wait(): Waits for another thread to notify it.
- I/O Operations: Like reading a file.
Example (Sleep):
try {
Thread.sleep(2000); // Waits 2 seconds (WAITING state)
} catch (InterruptedException e) {
e.printStackTrace();
}
Example (Blocked due to Lock):
synchronized(lockObject) {
// Thread is RUNNING
}
// Another thread trying to enter is BLOCKED
4. Blocked/Waiting → Runnable (Ready Again)
After the pause ends, the thread goes back to Runnable, waiting for CPU time.
Example:
// After sleep(2000) finishes, thread is RUNNABLE again
5. Running → Terminated (Thread Finishes)
When run() completes or stop() is called, the thread dies.
Example:
public void run() {
System.out.println("Done!");
} // Thread TERMINATES after this
Java Main Thread
Every Java program runs in a thread by default—the Main Thread. It’s created automatically when your program starts, and it’s responsible for running the main() method.
How Does the Main Thread Work?
When you execute a Java program:
- The JVM creates the Main Thread.
- It starts executing code from public static void main(String[] args).
- If no other threads are created, the entire program runs in this single thread.
Example:
public class MainThreadExample {
public static void main(String[] args) {
System.out.println("This is the Main Thread!");
// Checking the name of the current thread
System.out.println("Thread name: " + Thread.currentThread().getName());
}
}

You can also try this code with Online Java Compiler
Run Code
Output:
This is the Main Thread!
Thread name: main
Key Points About the Main Thread
- Default Name: It’s always named "main".
- Controls Program Flow: If the Main Thread finishes, the program ends (unless other threads are still running).
- Can Create Other Threads: You can spawn new threads from the Main Thread.
Example (Main Thread Creating Another Thread):
public class MainWithChildThread {
public static void main(String[] args) {
System.out.println("Main Thread starts.");
// Creating a new thread
Thread childThread = new Thread(() -> {
System.out.println("Child Thread running.");
});
childThread.start(); // Starts the child thread
System.out.println("Main Thread ends.");
}
}

You can also try this code with Online Java Compiler
Run Code
Possible Output:
Main Thread starts.
Main Thread ends.
Child Thread running.
(Note: The order of last two lines can swap because threads run independently.)
Why Is the Main Thread Important?
- It’s the starting point of every Java application.
- If it crashes, the whole program can fail (unless other threads are daemon threads).
- You can control other threads from it (start, interrupt, or wait for them).
What Happens If Main Thread Finishes Early?
If the Main Thread completes but other threads are still running:
- The program keeps running until all non-daemon threads finish.
- If you want the program to exit when the Main Thread ends, mark other threads as daemon threads.
Example (Daemon Thread):
public class DaemonExample {
public static void main(String[] args) {
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("Daemon thread running...");
}
});
daemonThread.setDaemon(true); // Set as daemon
daemonThread.start();
System.out.println("Main Thread exiting.");
}
}

You can also try this code with Online Java Compiler
Run Code
Output:
Main Thread exiting.
(Daemon thread terminates automatically when main ends)
How to Create Threads in Java?
Threads in Java can be created in two simple ways:
- By extending the Thread class
- By implementing the Runnable interface
Let's discuss both methods with proper examples.
Method 1: Extending Thread Class
This is the simpler way to make a thread. You just create a new class that extends Thread and override its run() method.
Example:
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running");
}
}
public class Main {
public static void main(String args[]) {
MyThread t1 = new MyThread();
t1.start(); // Starts the thread
}
}
What's happening here?
- We created MyThread class that extends Thread
- We wrote our code in the run() method
- In main(), we created thread object t1 and started it with start()
Method 2: Implementing Runnable Interface
This is the better way because Java doesn't support multiple inheritance. Since we're already extending Thread class in first method, we can't extend any other class.
Example:
class MyRunnable implements Runnable {
public void run() {
System.out.println("Thread using Runnable is running");
}
}
public class Main {
public static void main(String args[]) {
MyRunnable myRunnable = new MyRunnable();
Thread t2 = new Thread(myRunnable);
t2.start();
}
}
What's happening here?
- We created MyRunnable class that implements Runnable
- We wrote our code in the run() method
- In main(), we created Thread object and passed our Runnable to it
- Then we started the thread with start()
Which method is better?
Most programmers prefer the second method (Runnable) because:
- It allows your class to extend another class if needed
- It's more flexible
- It follows good object-oriented design
Let's see both methods working together
// Method 1
class ThreadExample extends Thread {
public void run() {
System.out.println("Thread from Thread class");
}
}
// Method 2
class RunnableExample implements Runnable {
public void run() {
System.out.println("Thread from Runnable");
}
}
public class Main {
public static void main(String[] args) {
// Using Thread class
ThreadExample t1 = new ThreadExample();
t1.start();
// Using Runnable
RunnableExample r = new RunnableExample();
Thread t2 = new Thread(r);
t2.start();
}
}
Important Notes
- Always call start() method, not run() directly
- Calling run() directly will execute in same thread (no new thread created)
- One thread can only be started once
Using Lambda Expression (Java 8+)
For small tasks, we can use lambda to make it shorter:
public class LambdaThread {
public static void main(String[] args) {
// Using lambda with Runnable
Thread t = new Thread(() -> {
System.out.println("Thread using lambda");
});
t.start();
}
}
This is the simplest way to create a thread in modern Java.
Running Threads in Java
Now that we know how to create threads, let's understand how to actually run them and make them do useful work. Running threads is simple, but there are some important things you should know.
Starting Threads Properly
The key rule is: Always use start() method, not run() directly
Example of RIGHT way:
Thread t = new Thread(() -> {
System.out.println("Thread is running");
});
t.start(); // Correct - creates new thread
Example of WRONG way:
Thread t = new Thread(() -> {
System.out.println("This is wrong");
});
t.run(); // Wrong - runs in same thread
The difference is:
- start() → Creates new thread and calls run() in it
- run() → Just executes the code in current thread (no new thread)
Making Threads Do Actual Work
Let's see a more practical example where threads do real work:
class FileDownloader implements Runnable {
private String fileName;
public FileDownloader(String name) {
this.fileName = name;
}
public void run() {
System.out.println("Starting download: " + fileName);
// Simulate download time
try {
Thread.sleep(2000); // Wait 2 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Finished download: " + fileName);
}
}
public class Main {
public static void main(String[] args) {
// Create multiple download threads
new Thread(new FileDownloader("file1.txt")).start();
new Thread(new FileDownloader("file2.txt")).start();
new Thread(new FileDownloader("file3.txt")).start();
System.out.println("All downloads started in background");
}
}

You can also try this code with Online Java Compiler
Run Code
Output might look like:
All downloads started in background
Starting download: file1.txt
Starting download: file2.txt
Starting download: file3.txt
Finished download: file1.txt
Finished download: file2.txt
Finished download: file3.txt
Important Thread Methods
Let’s take a look at some useful methods for controlling threads:
sleep() - Pauses thread for specified time
Thread.sleep(1000); // Sleep for 1 second
join() - Wait for thread to finish
Thread t = new Thread(/*...*/);
t.start();
t.join(); // Main thread waits here until t finishes
isAlive() - Check if thread is running
if (t.isAlive()) {
System.out.println("Thread is still running");
}
Real-World Example: Parallel Processing
Let's say we need to process 1000 numbers. We can split the work between threads:
class NumberProcessor extends Thread {
private int start;
private int end;
public NumberProcessor(int s, int e) {
start = s;
end = e;
}
public void run() {
for (int i = start; i <= end; i++) {
System.out.println("Processing: " + i);
}
}
}
public class Main {
public static void main(String[] args) {
// Create 4 threads to process numbers 1-1000
new NumberProcessor(1, 250).start();
new NumberProcessor(251, 500).start();
new NumberProcessor(501, 750).start();
new NumberProcessor(751, 1000).start();
}
}

You can also try this code with Online Java Compiler
Run Code
Key Points to Remember:
- Thread scheduling is controlled by JVM/OS - you can't predict exact order
- Threads run independently once started
- Use sleep() for delays, join() to wait for completion
- Avoid calling run() directly - always use start()
Common Mistakes to Avoid:
- Starting same thread multiple times (IllegalThreadStateException)
- Not handling InterruptedException
- Assuming threads will execute in specific order
Checking States of Thread in Java
Threads may be in variable states and there are occasions when one may need to find out the state a thread is in. Java contains easy methods to determine the status of threads. Here is how this will go with some simple examples.
Thread State Checker
Each thread has a getState()method that gives the current state. The states may be
- NEW - Thread created and not started
- RUNNABLE - Thread is runnable or ready to run
- BLOCKED - Thread awaits a lock
- WAITING - Waiting forever thread
- TIMED_WAITING - The waiting of the thread with definite time
- TERMINATED - Thread terminated execution
Let’s see how to check these states:
public class ThreadStates {
public static void main(String[] args) throws Exception {
Thread myThread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// NEW state
System.out.println("After creation: " + myThread.getState());
myThread.start();
// RUNNABLE state
System.out.println("After start: " + myThread.getState());
// Let main thread wait a bit
Thread.sleep(100);
// TIMED_WAITING state (because of sleep)
System.out.println("During sleep: " + myThread.getState());
// Wait for thread to finish
myThread.join();
// TERMINATED state
System.out.println("After completion: " + myThread.getState());
}
}

You can also try this code with Online Java Compiler
Run Code
Output:
After creation: NEW
After start: RUNNABLE
During sleep: TIMED_WAITING
After completion: TERMINATED
Checking Blocked State Example
Threads go to BLOCKED state when waiting for a lock:
public class BlockedState {
private static final Object lock = new Object();
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
synchronized(lock) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized(lock) {
System.out.println("Got the lock");
}
});
t1.start();
Thread.sleep(100); // Ensure t1 gets lock first
t2.start();
Thread.sleep(100); // Let t2 try to get lock
System.out.println("t2 state: " + t2.getState()); // BLOCKED
}
}
Real Use Case: Monitoring Threads
Here's how you might use thread states in a real program:
public class WorkerThread extends Thread {
public void run() {
System.out.println("Working...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("I was interrupted!");
}
}
}
public class ThreadMonitor {
public static void main(String[] args) throws Exception {
WorkerThread worker = new WorkerThread();
// Start monitoring
new Thread(() -> {
while(true) {
System.out.println("Worker state: " + worker.getState());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
break;
}
if(worker.getState() == Thread.State.TERMINATED) {
System.out.println("Worker finished!");
break;
}
}
}).start();
worker.start();
worker.join();
}
}
Concurrency Problems in Java Threads
When multiple threads work together, they can cause problems if not handled properly. Let's look at the most common issues with simple examples.
1. Race Condition Problem
This happens when multiple threads try to change the same data at the same time.
Example: Bank Account Problem
class BankAccount {
private int balance = 100;
public void withdraw(int amount) {
if(balance >= amount) {
try {
Thread.sleep(100); // Simulate processing time
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" withdrew " + amount);
}
}
public int getBalance() {
return balance;
}
}
public class RaceConditionDemo {
public static void main(String[] args) throws InterruptedException {
BankAccount account = new BankAccount();
Thread t1 = new Thread(() -> account.withdraw(50));
Thread t2 = new Thread(() -> account.withdraw(50));
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final balance: " + account.getBalance());
}
}
What's wrong here?
- Both threads check balance at same time (both see 100)
- Both proceed to withdraw
- Final balance becomes negative (should be 0)
2. Deadlock Problem
When two threads wait for each other forever.
Example:
class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public synchronized void greet(Friend other) {
System.out.println(name + " greeting " + other.name);
other.respond(this);
}
public synchronized void respond(Friend other) {
System.out.println(name + " responding to " + other.name);
}
}
public class DeadlockDemo {
public static void main(String[] args) {
Friend alice = new Friend("Alice");
Friend bob = new Friend("Bob");
new Thread(() -> alice.greet(bob)).start();
new Thread(() -> bob.greet(alice)).start();
}
}
What happens?
- Alice locks herself, tries to call Bob
- Bob locks himself, tries to call Alice
- Both wait forever for other's lock
3. Thread Starvation
When some threads don't get chance to run because others are always running.
Example:
public class StarvationDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread greedy = new Thread(() -> {
while(true) {
synchronized(lock) {
System.out.println("Greedy thread running");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread regular = new Thread(() -> {
while(true) {
synchronized(lock) {
System.out.println("Regular thread got chance!");
}
}
});
greedy.setPriority(Thread.MAX_PRIORITY);
regular.setPriority(Thread.MIN_PRIORITY);
greedy.start();
regular.start();
}
}
What happens?
- Greedy thread almost always gets the lock
- Regular thread rarely gets chance to run
Solutions to These Problems
1. For Race Conditions: Use synchronized blocks
public synchronized void withdraw(int amount) {
// method body
}
2. For Deadlocks: Always acquire locks in same order
// Instead of:
// thread1: lock A then B
// thread2: lock B then A
// Do:
// thread1: lock A then B
// thread2: lock A then B
3. For Starvation: Use fair locks or proper priorities
Lock lock = new ReentrantLock(true); // fair lock
Advantages of Creating Threads
- Improved performance: Threads enable a program to perform several tasks concurrently resulting in faster applications. Another example is that a web server is able to accommodate numerous clients at a time rather than processing each one at a time thus giving the client less wait time.
- Responsive Applications: Threads allow having some intensive operations going in the background allowing the main program to remain responsive. This avoids freezing of the user interface resulting in the enhancement of user experience.
- Optimal Resource Utilization: Threads use no memory as they exist in the same memory area as the rest of the program, thus it is not expensive to make threads like it is when creating processes. This uses CPU and memory more efficiently, in particular systems with fewer resources.
- Simple Design: Other issues (such simulations or real-time processing of data) can simply be described through threads. This can be split by doing one task at a time which results in a tidier code.
- More efficient Hardware Utilization: Currently the CPUs are multiple-core, and programs can make full use of them with the presence of threads. Most of the CPU power available would just go to waste without the threads.
- Real-Time Processing: Threads can instantly perform real-time sensitive operations (such as video streaming or mid-game). High priorities threads can host critical operations and background tasks can be founded on low priorities.
Frequently Asked Questions
How will the execution be when several threads call the same method?
When several threads invoke an identical method they run in parallel and a race conditional is possible when the method in question changes shared data. To avoid this, make sure to use synchronized and limit one thread to run at any one time.
Does a thread have an option of restarting after it has ended?
No, a terminated thread can not be restarted. You have to instantiate a new thread. An attempt to call start() on a thread that is dead throws IllegalThreadStateException.
What is the means of communication between the threads?
A thread communicates by sharing variables, or through the synchronizing functions wait(), notify() and notifyAll(). This should be synchronized well to prevent corruption of data.
Conclusion
We have also got to know about threads in Java: their functioning, their life cycle and how to create and manipulate them in this article. We have discussed states of thread, issues of concurrency such as race condition and dead locks, and their remedies. Threads enhance performance and responsiveness, but they have to be handled carefully that they may not cause problems.