1. Core thread states in Java
In Java, a thread is not just “running” or “stopped.” It has a full lifecycle, and at each stage a thread behaves differently. Understanding these stages is the key to writing stable multithreaded applications and to debugging strange “hangs” and “unexpected terminations.”
What states are there?
Java defines the following main thread states (they are listed in the enum Thread.State):
| State | Description |
|---|---|
|
The thread is created but not yet started (new Thread(...), but start() hasn’t been called) |
|
The thread is ready to run or is running right now |
|
The thread is waiting for a monitor to be released (blocked on a synchronized block) |
|
The thread is waiting for another thread to “wake it up” (for example, via Object.wait()) |
|
The thread is waiting with a timeout (for example, Thread.sleep(1000), wait(1000), join(1000)) |
|
The thread has finished execution |
Fun fact:
In older books and articles you may encounter different names or slightly different diagrams. But since Java 5+, these states are considered the standard.
Visual diagram of the thread lifecycle
stateDiagram-v2
[*] --> NEW
NEW --> RUNNABLE: start()
RUNNABLE --> BLOCKED: attempt to enter synchronized, but monitor is busy
BLOCKED --> RUNNABLE: monitor released
RUNNABLE --> WAITING: wait(), join()
RUNNABLE --> TIMED_WAITING: sleep(), wait(timeout), join(timeout)
WAITING --> RUNNABLE: notify()/notifyAll(), join() completed
TIMED_WAITING --> RUNNABLE: timeout expired / notify()/notifyAll()
RUNNABLE --> TERMINATED: run() finished
WAITING --> TERMINATED: run() finished (rare)
TIMED_WAITING --> TERMINATED: run() finished (rare)
2. Thread management methods
Sleeping: Thread.sleep(long millis)
Sometimes a thread needs to “sleep” so it doesn’t interfere with others or while it waits for an event. The Thread.sleep(ms) method puts the thread into the
TIMED_WAITING state for the specified number of milliseconds.
System.out.println("Thread is going to sleep for 2 seconds...");
Thread.sleep(2000); // Sleep for 2 seconds
System.out.println("Thread woke up!");
- After sleeping, the thread returns to the
state (ready to work).RUNNABLE - If the thread is interrupted while sleeping, an InterruptedException is thrown.
Waiting for another thread to finish: join()
Sometimes you need not just to start a thread but to wait until it finishes. For this, Java provides the join() method:
Thread t = new Thread(() -> {
System.out.println("Working...");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("Done!");
});
t.start();
System.out.println("Waiting for thread t to finish...");
t.join(); // The current thread (e.g., main) waits for t
System.out.println("Thread t has finished!");
Here, the main thread starts waiting as soon as join() is called. All this time it is in the
WAITING state until thread
t finishes execution. There is also a timed variant —
join(long millis). In that case the thread waits for a limited time, and its state will be
TIMED_WAITING.
Interrupting a thread: interrupt()
Sometimes you need to politely ask a thread to finish its work, for example, if the user clicked “Cancel.” For this, use the interrupt() method:
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// Working...
}
System.out.println("Thread finished due to interruption!");
});
t.start();
// ... after some time:
t.interrupt(); // Signal to the thread: "time to stop"
It’s important to understand that calling interrupt() doesn’t kill a thread instantly. It only sets a special flag. The thread itself should periodically check this flag via isInterrupted() and terminate on its own. If the thread is sleeping (sleep()) or waiting (wait()) at that moment, it will not just see the flag — it will immediately get an InterruptedException. This is the “proper” way to stop threads in Java: the program doesn’t forcefully terminate them, it notifies them that it’s time to finish.
3. Examples of transitions between states
Let’s look at examples of how a thread “travels” through its states.
Example 1: NEW
→ RUNNABLE
→ TERMINATED
NEWRUNNABLETERMINATEDThread t = new Thread(() -> System.out.println("Hello!"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE (or TERMINATED if the thread is very fast)
t.join();
System.out.println(t.getState()); // TERMINATED
Example 2: RUNNABLE
→ TIMED_WAITING
→ RUNNABLE
→ TERMINATED
RUNNABLETIMED_WAITINGRUNNABLETERMINATEDThread t = new Thread(() -> {
try {
System.out.println("Going to sleep...");
Thread.sleep(1000); // TIMED_WAITING
System.out.println("Woke up!");
} catch (InterruptedException e) {
System.out.println("Thread interrupted!");
}
});
t.start();
Example 3: RUNNABLE
→ WAITING
with join()
RUNNABLEWAITINGThread t1 = new Thread(() -> {
try { Thread.sleep(500); } catch (InterruptedException ignored) {}
System.out.println("t1 finished");
});
Thread t2 = new Thread(() -> {
try {
t1.join(); // t2 waits for t1, is in WAITING
System.out.println("t2 waited for t1");
} catch (InterruptedException ignored) {}
});
t1.start();
t2.start();
Example 4: BLOCKED
BLOCKEDObject lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
System.out.println("t1 exited the synchronized block");
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2 entered synchronized");
}
});
t1.start();
Thread.sleep(100); // Let t1 acquire the lock
t2.start();
Thread.sleep(100); // Let t2 try to enter synchronized
System.out.println("t2 state: " + t2.getState()); // BLOCKED
4. How to get a thread’s state? Methods isAlive() and getState()
- isAlive() — returns true if the thread has been started and hasn’t finished yet (
means false).TERMINATED - getState() — returns the current state of the thread (a value from the Thread.State enum).
Thread t = new Thread(() -> {});
System.out.println(t.isAlive()); // false (NEW)
t.start();
System.out.println(t.isAlive()); // true (RUNNABLE/WAITING/...)
t.join();
System.out.println(t.isAlive()); // false (TERMINATED)
5. Why you can’t “kill” a thread directly and other practical advice
There is no “kill” method!
Java doesn’t have a method that lets you “kill” a thread on command. Why? Because it’s unsafe: if a thread holds some resource (a file, a connection, a lock), forcefully destroying it may leave the system in an inconsistent state.
Deprecated methods: stop(), suspend(), resume()
In very old versions of Java, there were methods stop(), suspend(), resume(). They are now marked as @Deprecated and using them is strongly discouraged. Why?
- stop() can kill a thread at any moment, leaving data in an inconsistent state.
- suspend() can “freeze” a thread that holds a lock, and then the entire program can hang.
- resume() may not be able to “unfreeze” a thread if it has already finished.
Modern approach:
A thread should terminate gracefully on its own by responding to the interrupt flag (isInterrupted()) or other signals.
Best practices
- Do not call methods that are marked as deprecated.
- Use the interrupt flag to stop a thread (interrupt() and checking isInterrupted()).
- Monitor thread states during debugging — this will help you find hangs and deadlocks.
- Don’t forget about join() if you need to wait for a thread to finish.
6. Common mistakes when working with the thread lifecycle
Mistake #1: Starting a thread again.
In Java, you can start a thread only once. If you call start() a second time, you’ll get an IllegalThreadStateException. If you need to repeat a task, create a new Thread object.
Thread t = new Thread(() -> {});
t.start();
t.start(); // Will throw an exception!
Mistake #2: Confusing run() and start().
Calling run() directly does not run the code in a new thread — it executes in the current one (for example, in main). Only start() actually launches a new thread.
Mistake #3: Not handling InterruptedException.
If a thread is sleeping or waiting and it gets interrupted, an InterruptedException will be thrown. If you ignore it, the thread may “sleep forever” or terminate unexpectedly.
Mistake #4: Not checking the thread state.
Sometimes a program hangs because one thread is waiting for another that has already finished or will never start. Use getState() and isAlive() for diagnostics.
Mistake #5: Using deprecated thread-control methods.
Methods stop(), suspend(), resume() are harmful. Don’t use them, even if you really want to “fix everything quickly.”
GO TO FULL VERSION