Back to wait/notify, sleep/yield & Producer-Consumer
Q2Question 2 of 6

wait() vs sleep() vs yield()?

Key Concepts
  • wait() releases the monitor lock and waits for notify(); used for inter-thread communication.
  • sleep() does NOT release any lock and pauses for a fixed time; used for timed delays.
  • yield() is a scheduler hint that may be ignored; the thread stays RUNNABLE.
  • wait() must be called inside a synchronized block; sleep() and yield() can be called anywhere.
  • The most common bug: using sleep() inside synchronized to wait for a condition (holds the lock, blocks everyone).
  • Use wait() or Condition.await() for condition-based waiting; use sleep() only outside synchronized blocks.

Definition

wait() releases the lock and waits for a signal (communication), sleep() holds the lock and pauses for a time (delay), and yield() is an unreliable scheduler hint.
  • wait() – releases the monitor lock, enters WAITING state, resumes only on notify()/notifyAll(). Must be inside synchronized. Purpose: inter-thread communication.
  • sleep(millis) – pauses the current thread for the specified time, does NOT release any lock, enters TIMED_WAITING state. Purpose: timed delay.
  • yield() – hints the scheduler to let other threads of equal priority run. The scheduler may ignore it. Thread stays RUNNABLE. Purpose: cooperative scheduling (rarely useful).

wait() is a teller going to the break room and leaving the vault key on the desk. "Call me when the VIP arrives." Key released. sleep() is a teller taking a nap at their desk with the vault key in their pocket. Nobody else can enter the vault. yield() is a teller saying "I will let the next person go first," but the manager might say "No, you keep going."

Understanding the Concepts

How do the three methods compare?

| Feature | wait() | sleep() | yield() |
|---|---|---|---|
| Releases lock? | Yes | No | N/A |
| Thread state | WAITING | TIMED_WAITING | RUNNABLE |
| Resumes when | notify/notifyAll | Timeout or interrupt | Scheduler decides |
| Requires | synchronized block | Nothing | Nothing |
| Purpose | Communication | Timed delay | Scheduler hint |
| Static? | No (instance method) | Yes (Thread.sleep) | Yes (Thread.yield) |

What is the common bug with sleep inside synchronized?

JAVA
package com.bank.core;

public class SleepInsideSyncBug {
    private static final Object lock = new Object();
    private static boolean batchComplete = false;

    // WRONG: sleep holds the lock
    public static void wrongWaitForBatch() throws InterruptedException {
        synchronized (lock) {
            while (!batchComplete) {
                Thread.sleep(1000); // Lock held for 1 second each iteration!
                // All other threads waiting for 'lock' are BLOCKED.
            }
        }
    }

    // CORRECT: wait releases the lock
    public static void correctWaitForBatch() throws InterruptedException {
        synchronized (lock) {
            while (!batchComplete) {
                lock.wait(1000); // Lock RELEASED during wait!
                // Other threads can enter synchronized and set batchComplete.
            }
        }
    }
}

When should you use each?

  • wait(): when you need to wait for a condition that another thread will change. The lock release is essential so the other thread can acquire the lock, change the state, and notify.
  • sleep(): when you need a timed delay and you are NOT inside a synchronized block. Example: rate limiting, retry backoff.
  • yield(): almost never. Use Thread.onSpinWait() (Java 9) for spin-wait loops instead.

What should a senior engineer know?

  • sleep(0) vs yield(): Thread.sleep(0) on some OS implementations yields the time slice, similar to yield(). Neither is reliable.
  • Thread.onSpinWait() (Java 9): in spin-wait loops, this emits a CPU PAUSE instruction that reduces power consumption and improves hyperthreading. It is the modern replacement for yield() in spin loops.
  • LockSupport.parkNanos(): the lowest-level parking primitive. Used internally by ReentrantLock, CountDownLatch, and other concurrency utilities.

What are common mistakes?

A common mistake is not understanding the concept fully before using it in production banking applications.

Wrong approach:

JAVA
package com.bank.service;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, double amount) {
        // WRONG: using double for money
        double balance = getBalance(fromAccount);
        balance = balance - amount;
    }
    private double getBalance(String id) { return 10000.0; }
}

Correct approach:

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
        // CORRECT: using BigDecimal for money
        BigDecimal balance = getBalance(fromAccount);
        balance = balance.subtract(amount);
    }
    private BigDecimal getBalance(String id) { return new BigDecimal("10000.00"); }
}

Always use BigDecimal for monetary calculations in banking systems to avoid floating-point precision errors.

What are the edge cases?

Edge cases can cause unexpected behavior in banking applications. Here are some to watch for.

JAVA
package com.bank.edge;

public class EdgeCaseDemo {
    public static void main(String[] args) {
        // Edge case 1: null handling
        String accountId = null;
        try {
            System.out.println(accountId.length());
        } catch (NullPointerException e) {
            System.out.println("Null account ID");
        }

        // Edge case 2: empty vs null
        String emptyId = "";
        System.out.println(emptyId.isEmpty());  // true
        System.out.println(emptyId == null);     // false

        // Edge case 3: boundary values
        java.math.BigDecimal zero = java.math.BigDecimal.ZERO;
        java.math.BigDecimal negativeBalance = new java.math.BigDecimal("-0.01");
        System.out.println(zero.compareTo(negativeBalance) > 0);  // true
    }
}
TEXT
Null account ID
true
false
true

Always test with null values, empty strings, boundary values, and negative amounts in banking applications.

What are common interview follow-up questions?

Interviewers often follow up with these related questions:

  • How does this concept apply to a high-throughput banking transaction system?
  • What are the performance implications in a production environment?
  • How would you debug issues related to this concept?
  • What tools would you use to monitor this in production?
  • How does this behave differently in Java 8 vs Java 17+?

When answering follow-up questions, always relate your answer back to practical experience. For example, at National Bank of India, Rajesh debugged a production issue related to this concept by using JFR recordings and heap dumps. Providing concrete examples from banking systems shows the interviewer that you understand both the theory and the practice.

Visual Overview

DIAGRAM
  wait():    synchronized(lock) { lock.wait(); }
             Thread: RUNNABLE ──▶ WAITING (lock RELEASED)
             Resumes: notify() → re-acquires lock → RUNNABLE

  sleep():   Thread.sleep(5000);
             Thread: RUNNABLE ──▶ TIMED_WAITING (lock HELD)
             Resumes: 5 seconds later → RUNNABLE

  yield():   Thread.yield();
             Thread: RUNNABLE ──▶ RUNNABLE (scheduler hint, may be ignored)

Key Takeaway

The critical difference: wait() releases the monitor lock and enters WAITING until notify(). sleep() does NOT release any lock and pauses for a fixed time. yield() is an unreliable scheduler hint. The most common bug is calling sleep() inside a synchronized block to wait for a condition: the lock is held during the entire sleep, blocking all other threads. Use wait() or Condition.await() instead.

Remember one sentence: wait() releases the lock for communication, sleep() holds the lock for delay, and yield() is an unreliable hint you should almost never use.

---

Share
Use ← → arrow keys to navigate