Table of contents
1.
Introduction
2.
Thread Life Cycle
2.1.
1. New State
2.2.
2. Runnable State
2.3.
3. Running State
2.4.
4. Blocked/Waiting State
2.5.
5. Terminated State
3.
Working of Thread States
3.1.
1. New → Runnable (Thread Starts)
3.2.
2. Runnable → Running (CPU Executes Thread)
3.3.
3. Running → Blocked/Waiting (Paused Temporarily)
3.4.
4. Blocked/Waiting → Runnable (Ready Again)
3.5.
5. Running → Terminated (Thread Finishes)
4.
Java Main Thread
4.1.
How Does the Main Thread Work?
4.2.
Key Points About the Main Thread
4.3.
Why Is the Main Thread Important?
5.
What Happens If Main Thread Finishes Early?
6.
How to Create Threads in Java?
6.1.
Method 1: Extending Thread Class
6.2.
Method 2: Implementing Runnable Interface
6.3.
Which method is better?
6.4.
Important Notes
6.5.
Using Lambda Expression (Java 8+)
7.
Running Threads in Java
7.1.
Starting Threads Properly
7.2.
Making Threads Do Actual Work
8.
Real-World Example: Parallel Processing
9.
Checking States of Thread in Java
9.1.
Thread State Checker
10.
Real Use Case: Monitoring Threads
11.
Concurrency Problems in Java Threads
11.1.
1. Race Condition Problem
11.2.
2. Deadlock Problem
11.3.
3. Thread Starvation
12.
Solutions to These Problems
12.1.
1. For Race Conditions: Use synchronized blocks
12.2.
2. For Deadlocks: Always acquire locks in same order
12.3.
3. For Starvation: Use fair locks or proper priorities
13.
Advantages of Creating Threads
14.
Frequently Asked Questions
14.1.
How will the execution be when several threads call the same method?
14.2.
Does a thread have an option of restarting after it has ended?
14.3.
What is the means of communication between the threads?
15.
Conclusion
Last Updated: Jun 22, 2025
Easy

Threads in Java

Author Sinki Kumari
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Java threads enable a program to use several tasks simultaneously. Consider them as your mini-workers within your program and each of them is assigned a distinct task on the same program. This makes applications more efficient and faster particularly when performing operations such as downloading files without affecting the user interface.

Threads in Java

Here, we are going to get to know how thread works in java, its life cycle, various states, create and manage them. we shall also examine typical difficulties that emerge when working with threads & why they are handy in practice.

Thread Life Cycle

A Java thread passes through various phases since its inception and when it finally concludes its mission. This is known as a life cycle of a thread. 

There are the following 5 life cycle states:

  • New
     
  • Runnable
     
  • Running
     
  • Blocked/Waiting
     
  • Terminated


So, let us look at each of them : 

1. New State

A thread born and not yet started is in the New state. At this stage, there is a thread, which is not yet scheduled to be run.

Example:

Thread myThread = new Thread();  // Thread is NEW state  


In this case, myThread is created it is not run yet.

2. Runnable State

As the method start() is invoked, the thread enters the Runnable state. It is now prepared to run but has not been picked by the CPU yet.

Example:

myThread.start();  //Thread goes to RUNNABLE state  


The thread is in turn waiting to run.

3. Running State

The thread will be put under Running state when the thread scheduler selects it in the Runnable pool. In this case the run() method of thread is called.

Example:

public void run(){  
    System.out.println("Thread is being executed");  
}  


The thread is performing when this code is being executed.

4. Blocked/Waiting State

A thread may suspended because of:

  • Awaiting I/O tasks (such as the reading of files).
     
  • Wait on lock (in multithreaded sync).
     
  • Sleeping() or waiting().


Example:

Thread.sleep(1000); // Waiting state 1 second  


The thread is just hanging but not killed can be continued further.

5. Terminated State

After the run() method terminates , or when the thread is stopped, the thread then enters Terminated state. It will not be able to restart.

Example:

public void run() {  
    System.out.println("Thread done");  
}  // After execution, thread TERMINATES  


Visual Representation

NEW → RUNNABLE → RUNNING → (BLOCKED/WAITING) → TERMINATED 

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.

Live masterclass