Thread.sleep()
Thread.sleep() tells the current thread to pause execution for a specified amount of time, handing control back to the CPU for other threads to run. It is one of the most frequently used methods in concurrent Java — and one of the most frequently misused. This page covers the API, the common traps, and what is actually happening at the OS and JVM level when you call it.
The sleep() API
Thread.sleep() is a static native method in java.lang.Thread. It always applies to the currently executing thread — you cannot sleep another thread from the outside.
// Overload 1: milliseconds only
Thread.sleep(long millis) throws InterruptedException
// Overload 2: milliseconds + nanoseconds (finer granularity)
Thread.sleep(long millis, int nanos) throws InterruptedException
The method throws InterruptedException — a checked exception — so you must either declare it with throws or catch it. More on that in a moment.
Note: Since Java 19,
Thread.sleep(Duration duration)is also available, accepting ajava.time.Durationfor more readable code.
Basic Usage
public class SleepDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("Start: " + System.currentTimeMillis());
Thread.sleep(2000); // pause for 2 seconds
System.out.println("End: " + System.currentTimeMillis());
}
}
Output:
Start: 1718280000000
End: 1718280002003
The gap is approximately 2000 ms. The small overshoot (3 ms here) is normal — the OS scheduler does not guarantee precision to the millisecond.
Warning:
Thread.sleep()does not guarantee an exact pause duration. The thread will sleep at least the requested time, but may wake up slightly later depending on system load and OS scheduling resolution. Never use it for high-precision timing.
Sleeping a Specific Thread
Because sleep() is static, it always affects the calling thread, not the Thread object you might have a reference to:
Thread worker = new Thread(() -> {
try {
System.out.println("Worker sleeping…");
Thread.sleep(1000); // sleeps THIS thread (worker), not main
System.out.println("Worker awake!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore interrupt flag
System.out.println("Worker was interrupted!");
}
});
worker.start();
worker.join(); // main waits for worker to finish
Output:
Worker sleeping…
Worker awake!
Handling InterruptedException Correctly
This is the most critical part of using sleep(). When another thread calls interrupt() on a sleeping thread, sleep() throws InterruptedException and clears the thread’s interrupt flag.
The wrong way — silently swallowing the exception
// BAD: interrupt signal is lost forever
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// do nothing — the interrupt is gone
}
The right way — restore the interrupt flag
// GOOD: restore the flag so callers can detect it
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // re-set the flag
// optionally: log, clean up, or return early
}
Tip: If your method signature declares
throws InterruptedException, you can simply let the exception propagate instead of catching it yourself. This is often the cleanest approach in library or framework code.
Propagating vs catching — when to choose which
| Scenario | Recommended approach |
|---|---|
| Short utility method used in concurrent context | throws InterruptedException — propagate it |
Runnable.run() (cannot declare checked exceptions) | Catch and call Thread.currentThread().interrupt() |
| Top-level task that should abort on interrupt | Catch, clean up resources, then return or log |
Real-World Pattern: Polling with Sleep
A common pattern is polling a condition at intervals. Use a loop that checks whether the thread has been interrupted so it can shut down cleanly:
public class PollingTask implements Runnable {
private volatile boolean dataReady = false; // set by another thread
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
if (dataReady) {
System.out.println("Data is ready — processing!");
break;
}
System.out.println("Waiting for data…");
try {
Thread.sleep(500); // check every 500 ms
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore flag
break; // exit the loop on interrupt
}
}
System.out.println("Task finished.");
}
}
Note: For production polling scenarios with precise intervals, consider
ScheduledExecutorServicefromjava.util.concurrentinstead — it is more accurate and handles thread management for you.
Using TimeUnit for Readable Sleep Calls
The java.util.concurrent.TimeUnit enum wraps Thread.sleep() with human-readable time units:
import java.util.concurrent.TimeUnit;
public class TimeUnitSleepDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("Sleeping for 3 seconds…");
TimeUnit.SECONDS.sleep(3); // clearer than Thread.sleep(3000)
System.out.println("Sleeping for 500 milliseconds…");
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("Done.");
}
}
TimeUnit.sleep() delegates to Thread.sleep() internally, so the behaviour and InterruptedException handling rules are exactly the same.
Tip: Prefer
TimeUnit.SECONDS.sleep(n)overThread.sleep(n * 1000)in any code that will be read by others — the intent is immediately clear.
Java 19+ Duration-Based sleep
Java 19 introduced a convenience overload that accepts a java.time.Duration:
import java.time.Duration;
// Java 19+
Thread.sleep(Duration.ofSeconds(2));
Thread.sleep(Duration.ofMillis(750));
This integrates naturally with the Date/Time API and eliminates unit-conversion arithmetic.
Does sleep() Release Locks?
No — and this is a critical distinction:
Thread.sleep()does not release anysynchronizedmonitors the sleeping thread holds.Object.wait()does release the monitor, allowing other threads to enter thesynchronizedblock.
public class SleepDoesNotReleaseLock {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread holder = new Thread(() -> {
synchronized (lock) {
System.out.println("Holder: acquired lock, sleeping…");
try {
Thread.sleep(3000); // holds lock while sleeping!
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Holder: woke up, releasing lock.");
}
});
Thread waiter = new Thread(() -> {
System.out.println("Waiter: trying to acquire lock…");
synchronized (lock) { // blocks until holder wakes
System.out.println("Waiter: finally got the lock.");
}
});
holder.start();
Thread.sleep(100); // let holder acquire lock first
waiter.start();
holder.join();
waiter.join();
}
}
Output:
Holder: acquired lock, sleeping…
Waiter: trying to acquire lock…
Holder: woke up, releasing lock.
Waiter: finally got the lock.
The waiter is blocked for the full 3 seconds because sleep() keeps the lock. If you need to release a lock while waiting, use wait() / notify() from Inter-Thread Communication.
Under the Hood
What happens in the JVM and OS
When your thread calls Thread.sleep(millis):
- The JVM calls the native
sleepsystem call (e.g.,nanosleepon Linux,SleepExon Windows). - The OS moves the thread from the RUNNABLE state to a timed-waiting queue (no CPU cycles consumed).
- A hardware timer is set. When it fires, the OS moves the thread back to the runnable queue.
- The JVM’s thread scheduler eventually picks the thread up and execution resumes after the
sleep()call.
The JVM reflects this as Thread.State.TIMED_WAITING. You can observe it:
Thread t = new Thread(() -> {
try { Thread.sleep(5000); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});
t.start();
Thread.sleep(100); // let t start sleeping
System.out.println(t.getState()); // TIMED_WAITING
Output:
TIMED_WAITING
Why precision is not guaranteed
Modern operating systems use a timer interrupt frequency (the “tick rate”) — commonly 100–1000 Hz on Linux, meaning the minimum timer resolution is 1–10 ms. A sleep(1) call may actually sleep for 1–15 ms depending on the platform. On Windows this is particularly noticeable until timeBeginPeriod(1) is called (which the JVM does in some configurations).
Sleep vs busy-wait
Never replace Thread.sleep() with a busy-wait loop to “check faster”:
// BAD: burns CPU on the calling thread
long end = System.currentTimeMillis() + 2000;
while (System.currentTimeMillis() < end) { /* spin */ }
// GOOD: zero CPU while waiting
Thread.sleep(2000);
A sleeping thread consumes essentially zero CPU. A spinning thread consumes 100% of one core.
Virtual threads and sleep (Java 21)
With Virtual Threads (Project Loom, Java 21), calling Thread.sleep() on a virtual thread does not block the underlying OS carrier thread. The virtual thread is unmounted from the carrier, freeing it to run other virtual threads. This makes sleep()-heavy code scale to millions of virtual threads without the OS overhead you would incur with platform threads.
Common Mistakes Summary
| Mistake | Why it hurts | Fix |
|---|---|---|
Swallowing InterruptedException with empty catch | Interrupt signal is lost; thread cannot be shut down cleanly | Call Thread.currentThread().interrupt() in the catch block |
Calling someThread.sleep(...) expecting it to sleep someThread | It sleeps the calling thread, not someThread | Use Thread.sleep() inside the target thread’s run() |
Using sleep() inside a synchronized block expecting lock release | Lock is held the whole time | Use wait() / notify() for lock-releasing waits |
| Assuming millisecond precision | OS scheduling can add extra delay | Accept the jitter; use ScheduledExecutorService for accuracy |
Related Topics
- Thread Life Cycle — See how
TIMED_WAITINGfits into the full lifecycle from NEW to TERMINATED. - Inter-Thread Communication — Use
wait()andnotify()when you need to release a lock while a thread waits. - Interrupting a Thread — Understand how
interrupt(),isInterrupted(), andInterruptedExceptionwork together. - Creating a Thread — The foundation: how to create and start threads before you pause them.
- Synchronization — Learn how
synchronizedmonitors interact with sleeping threads. - Thread Pools & Executors — Production-grade scheduling with
ScheduledExecutorServiceinstead of manual sleep loops.