Sleep vs Wait in Java: Lock, State and Wake-Up Differences

Sleep vs wait in Java compared with Java 25 examples, covering which method keeps the lock, the thread states, wake-up rules and modern alternatives.

Two timelines comparing a thread that pauses with Thread.sleep and keeps the lock with a thread that calls wait and releases it

The difference between sleep and wait in Java is the lock. Thread.sleep() pauses the current thread for a fixed time and keeps every monitor it holds, whereas Object.wait() releases the monitor of one object and waits until another thread notifies it or the timeout ends.

We use the sleep method when a thread must pause for a known time, for example between two retries of an HTTP call. We use wait() when a thread must stop until another thread changes some shared state, such as an order that becomes ready.

The following example runs both calls inside a synchronized block on the same object. The helper pause(), declared in section 1, keeps the monitor the same way Thread.sleep() does.

Object lock = new Object();
synchronized (lock) {
    lock.wait(100);              // releases lock for 100 ms, then takes it back
}
synchronized (lock) {
    pause(100);                  // keeps lock for the whole 100 ms
}
boolean held = Thread.holdsLock(lock);   // false

Both blocks wait 100 milliseconds, but only the first one lets other threads enter synchronized (lock) during that time. We look at each method, compare them in a table, test the lock behavior with two threads and pick the right tool for modern Java 25 code, which in many cases is neither of the two.

1. Pausing the Current Thread With Thread.sleep()

The sleep method is a static method of Thread that stops the current thread for at least the given time. During the pause, the thread is in the TIMED_WAITING state, and it keeps every lock it already holds. When the time is over, the thread becomes runnable again without any signal from another thread.

Java 25 offers three overloads. The Duration overload, added in Java 19, is the most readable one, so we prefer it in new code.

  • Thread.sleep(long millis) pauses for the given milliseconds.
  • Thread.sleep(long millis, int nanos) adds nanoseconds, although the real precision depends on the OS timer.
  • Thread.sleep(Duration duration) takes a java.time.Duration such as Duration.ofMillis(200).

Because the method is static, it always pauses the thread that calls it. Writing worker.sleep(500) compiles with a warning, but it pauses the current thread and never worker. If another thread interrupts the paused thread, the sleep method throws InterruptedException and clears the interrupt flag, so we restore the flag in the catch block with Thread.currentThread().interrupt().

Our snippets pause with the helper pause(), which parks the current thread through LockSupport. Like Thread.sleep(), it keeps every monitor the thread holds, so the lock behavior in the examples is the same.

static void pause(long ms) {
    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(ms));
}

The following example starts a cook thread that pauses inside synchronized (menu). A second thread, guest, tries to enter the same block while the cook is paused.

Object menu = new Object();
Thread cook = Thread.ofPlatform().start(() -> {
    synchronized (menu) {
        pause(300);
    }
});
pause(50);
Thread guest = Thread.ofPlatform().start(() -> {
    synchronized (menu) {
        // reads the menu
    }
});
pause(50);
Thread.State cookState = cook.getState();     // TIMED_WAITING
Thread.State guestState = guest.getState();   // BLOCKED
cook.join();
guest.join();

The guest thread is BLOCKED because the paused cook still owns the monitor of menu. Pausing while holding a lock makes every other thread that needs that lock wait for the full pause, so we keep pauses outside synchronized blocks.

A virtual thread behaves differently in one useful way. When a virtual thread calls the sleep method, the JDK unmounts it from its carrier thread, so the carrier can run other virtual threads during the pause. A paused platform thread keeps its OS thread busy for the whole time.

2. Waiting for a Condition With Object.wait()

The wait() method is an instance method of Object, so we call it on the object whose state we wait for. The calling thread must own that object’s monitor, which means it runs inside synchronized on the same object. Otherwise the call throws IllegalMonitorStateException.

When the thread calls wait(), it releases the monitor and goes to the WAITING state, or to TIMED_WAITING for wait(long). It wakes up in one of four cases.

  • Another thread calls notify() or notifyAll() on the same object.
  • The timeout of wait(long) or wait(long, int) ends.
  • Another thread interrupts it, and wait() throws InterruptedException.
  • It wakes up for no reason, which is called a spurious wakeup.

The thread must get the monitor back before wait() returns, so after a notification it sits in BLOCKED until the notifying thread leaves its synchronized block. Because of spurious wakeups and other threads that change the state first, we always call wait() inside a while loop that checks the condition.

Say a restaurant app has a guest thread that waits for its order, and a kitchen thread that marks the order as ready. The following Kitchen class keeps the state in the field orderReady, and both methods synchronize on the same Kitchen object.

class Kitchen {
    private boolean orderReady;

    synchronized void markReady() {
        orderReady = true;
        notifyAll();
    }

    synchronized String awaitOrder() throws InterruptedException {
        while (!orderReady) {
            wait();
        }
        return "served";
    }
}
Kitchen kitchen = new Kitchen();
try (ExecutorService pool = Executors.newVirtualThreadPerTaskExecutor()) {
    Future<String> order = pool.submit(kitchen::awaitOrder);
    pause(50);
    kitchen.markReady();
    String dish = order.get(1, TimeUnit.SECONDS);   // "served"
}

The call kitchen.markReady() enters the synchronized method while the guest is still inside awaitOrder(). That works only because wait() released the monitor. If the kitchen marks the order ready before the guest starts waiting, the while loop sees orderReady == true and never calls wait(), so the notification is not lost. The full producer and consumer pattern is in our guide to wait(), notify() and notifyAll().

3. Difference Between sleep and wait in Java

Both methods stop the current thread and both throw InterruptedException, which is why they look alike. They differ in what they do with locks and in what wakes the thread up, and the diagram puts the two cases next to each other.

Two timelines comparing a thread that pauses with Thread.sleep and keeps the lock with a thread that calls wait and releases it
The sleep method keeps the monitor for the whole pause, whereas wait() releases it until a notification or a timeout

Most rows follow from one fact, namely that wait() works on the monitor of one object, whereas the sleep method only affects the current thread.

FeatureThread.sleep()Object.wait()
Declared inThread, staticObject, final instance method
Which thread stopsThe current threadThe current thread, on the object it calls
Needs the monitorNo, works anywhereYes, otherwise IllegalMonitorStateException
Locks during the pauseKeeps all monitors and locksReleases only the monitor of that object
Thread stateTIMED_WAITINGWAITING, or TIMED_WAITING with a timeout
Wakes up whenThe time is over, or on interruptnotify(), notifyAll(), timeout, interrupt or a spurious wakeup
Overloadslong, long, int, Duration (Java 19)No arguments, long, long, int
Called in a loopNoYes, always in a while loop on the condition
Typical useA fixed delay, a retry backoff, a testWaiting until shared state changes

The key rule is that wait() releases the monitor and the sleep method does not. A thread that waits lets other threads change the state it waits for, whereas a thread that pauses inside synchronized blocks every thread that needs the same lock.

4. Checking Which Method Releases the Lock

Thread states make the lock behavior visible. In the following example, the holder thread calls wait(300) on table, and a second thread enters synchronized (table) while the holder waits.

Object table = new Object();
Thread holder = Thread.ofPlatform().start(() -> {
    synchronized (table) {
        try {
            table.wait(300);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
});
pause(50);
CountDownLatch entered = new CountDownLatch(1);
Thread visitor = Thread.ofPlatform().start(() -> {
    synchronized (table) {
        entered.countDown();
    }
});
boolean visitorGotIn = entered.await(100, TimeUnit.MILLISECONDS);   // true
Thread.State holderState = holder.getState();                       // TIMED_WAITING
holder.join();
visitor.join();

We can see that visitorGotIn is true while the holder is still in TIMED_WAITING. Compared with the cook in section 1, where the guest stayed BLOCKED, the only change is wait(300) in place of a pause.

There is one more detail. The wait() method releases only the monitor of the object it is called on. If the thread also holds the monitor of a second object from an outer synchronized block, it keeps that one, and other threads that need it stay blocked, which is a common cause of a deadlock.

5. Choosing Between sleep, wait and java.util.concurrent

Both methods are low-level tools. In application code written for Java 25, the classes of java.util.concurrent cover most cases with less code and fewer mistakes.

NeedOld styleBetter choice in Java 25
Delay between retriesThread.sleep() in a loopThe sleep method on a virtual thread, or a ScheduledExecutorService
Run a task every few secondsA loop with a pausescheduleAtFixedRate()
Wait until N tasks finishwait() and a counterCountDownLatch or ExecutorService.close()
Hand items from producer to consumerwait() and notifyAll() on a listA BlockingQueue
Wait for one async resultwait() on a flagCompletableFuture.get() or join()

For example, a payment client retries a failed call three times with a growing delay of 200, 400 and 800 milliseconds. On a virtual thread, the sleep method costs almost nothing, so a plain loop with Thread.sleep() is fine there. On a small pool of platform threads, the same loop occupies a pool thread for every pause, and a scheduled executor frees it.

Old code that uses wait() and notifyAll() also runs well on virtual threads, because since Java 24 (JEP 491) a virtual thread in Object.wait() unmounts from its carrier thread. New code still reads better with the higher-level classes listed in the Java concurrency guide.

6. Sleep vs Wait FAQs

Lock ownership and missed notifications are where the two methods trip people up, both in older thread code and in interviews.

6.1. Does Thread.sleep release the lock?

No. The sleep method keeps every monitor and every Lock the thread holds. Other threads that need one of those locks stay BLOCKED until the pause ends and the thread leaves the synchronized block.

6.2. Can we call wait() without synchronized?

No. The call compiles, but without the monitor of bell it throws IllegalMonitorStateException at runtime. The same rule applies to notify() and notifyAll().

Object bell = new Object();
bell.wait(10);                       // IllegalMonitorStateException

6.3. Why is sleep static but wait is not?

The sleep method always pauses the current thread, so it needs no target object, and a static method makes that clear. The wait() method works with the monitor of a specific object, so it belongs to Object and we call it on that object.

6.4. What happens if notify() is called before wait()?

The notification is lost, because notify() wakes only threads that are already waiting. A later wait() without a condition check waits forever. The while loop on a shared flag, as in section 2, fixes that case because the thread sees the flag and never calls wait().

6.5. Is wait(1000) the same as a one-second sleep?

No. Both stop the thread for up to one second, but wait(1000) releases the monitor and can return earlier on notify() or a spurious wakeup. A one-second sleep keeps the monitor and returns only after the full second or on an interrupt.

6.6. Which is better for polling a value?

Neither. A loop that pauses and checks a value wastes time between checks and adds delay to every change. We use a CountDownLatch, a CompletableFuture or a BlockingQueue, so the waiting thread wakes up at the moment the value is ready.

7. Conclusion

The sleep method pauses the current thread for a fixed time and keeps its locks. The wait() method needs the monitor of an object, releases it while waiting and returns after a notification, a timeout, an interrupt or a spurious wakeup, so it always goes in a while loop.

We use the sleep method for plain delays, preferably outside synchronized blocks, and wait() only for state that other threads change. For new code, BlockingQueue, CountDownLatch, CompletableFuture and scheduled executors replace most hand-written wait and notify logic.

8. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. Hi Lokesh,

    many thanks for your valuable posts .

    The only site I refer for any java queries is this !!!!!!!!!!

  2. you write in easy way…really you are doing good job ..james gosling invented java and lokesh is making easyjava :)

  3. Lokesh, What happens if someone dosent call notify( earlier thread called wait) ?

    will this keep the program in hanged state? will it not allow program to exit/terminate?

    • Tricky question.. In my view, no major impact on application just because one of several hundreds thread is blocked somewhere. Rest of application will run fine. This is very much clear and easy.

      No, program will not hang. According to theory, it should not allow to terminate and you might need to do it forcefully.

  4. Good points you are covering here, it will be more helpful if you take up the relatively new topics like concurrent package, annotations, generics, nio etc

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.