Usage and Differences of wait(), sleep(), yield(), and join()
Throughout this article, "current thread" refers to the thread that currently holds CPU resources and is actively executing.
wait() Method
The wait() method is defined in the Object class. It moves the current thread from the running state to the waiting (blocked) state, and releases the synchronization lock held by the current thread. Threads blocked by wait() can be woken up to the ready state by calling notify() or notifyAll() on the same object.
The full set of wait/notification APIs in Object are as follows:
wait(): Puts the current thread into waiting (blocked) state until another thread invokesnotify()ornotifyAll()on the same object, after which the current thread wakes up and enters the ready state.wait(long timeout): Puts the current thread into waiting (blocked) state, and the thread will automatically wake up after the specified timeout period, or be woken early bynotify()/notifyAll().wait(long timeout, int nanos): Similar to the timeout version, but allows finer granularity for timeout specification, and can also be woken by an interrupt from another thread.notify(): Wakes up a single thread that is waiting on the current object's monitor.notifyAll(): Wakes up all threads that are waiting on the current object's monitor.
Example usage of wait() and notify():
public class WaitNotifyDemo {
static class SignalThread extends Thread {
public SignalThread(String name) {
super(name);
}
@Override
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " invokes notify()");
notify();
}
}
}
public static void main(String[] args) {
SignalThread signalThread = new SignalThread("t1");
synchronized (signalThread) {
try {
System.out.println(Thread.currentThread().getName() + " starts t1");
signalThread.start();
// Block current main thread, release the lock on signalThread
signalThread.wait();
System.out.println(Thread.currentThread().getName() + " resumes execution");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Output:
main starts t1
t1 invokes notify()
main resumes execution
Key notes:
notify()andwait()depend on an object's synchronization monitor lock, and every object in Java has exactly one monitor lock. This is why these methods are defined in theObjectclass rather than theThreadclass.- Both methods can only be called within a
synchronizedblock or method.
sleep() Method
sleep() is a static method defined in the Thread class. It moves the current thread from the runing state to the sleeping (blocked) state. The core differences between sleep() and wait() are:
- The two methods belong to different classes:
sleep()belongs toThread,wait()belongs toObject sleep()does not release the synchronization lock held by the current thread, whilewait()releases the lock to allow other threads to use the synchronized block or method.wait(),notify(),notifyAll()can only be used in synchronized contexts, whilesleep()can be called in any context.sleep()must catch the checkedInterruptedException, whilewait/notifymethods do not require checked exception handling.
Example usage of sleep():
public class SleepDemo {
static class CountingSleepThread extends Thread {
public CountingSleepThread(String name) {
super(name);
}
@Override
public synchronized void run() {
try {
for (int i = 0; i < 10; i++) {
System.out.printf("%s: %d\n", this.getName(), i);
// Sleep for 100ms when i is divisible by 4
if (i % 4 == 0) {
Thread.sleep(100);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
CountingSleepThread thread = new CountingSleepThread("t1");
thread.start();
}
}
Output:
t1: 0
t1: 1
t1: 2
t1: 3
t1: 4
t1: 5
t1: 6
t1: 7
t1: 8
t1: 9
yield() Method
yield() is a static method defined in the Thread class. It makes the current thread yield CPU usage rights: it moves the current thread from the running state to the ready state, so that threads of the same or higher priority have a chance to be scheduled for execution. However, there is no guarantee that after yield() is called, other matching threads will get CPU time; the scheduler may also pick the current thread again to run immediately.
Example usage of yield():
public class YieldDemo {
static class YieldingThread extends Thread {
public YieldingThread(String name) {
super(name);
}
@Override
public synchronized void run() {
for (int i = 0; i < 10; i++) {
System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i);
// Invoke yield when i is divisible by 4
if (i % 4 == 0) {
Thread.yield();
}
}
}
}
public static void main(String[] args) {
YieldingThread t1 = new YieldingThread("t1");
YieldingThread t2 = new YieldingThread("t2");
t1.start();
t2.start();
}
}
Sample output:
t1 [5]:0
t2 [5]:0
t1 [5]:1
t1 [5]:2
t1 [5]:3
t1 [5]:4
t1 [5]:5
t1 [5]:6
t1 [5]:7
t1 [5]:8
t1 [5]:9
t2 [5]:1
t2 [5]:2
t2 [5]:3
t2 [5]:4
t2 [5]:5
t2 [5]:6
t2 [5]:7
t2 [5]:8
t2 [5]:9
Result explanation: Thread t1 did not switch to thread t2 after yielding when i was divisible by 4. This confirms that yield() only moves the current thread to the ready state, but does not guarantee that another thread will get CPU execution time, even if the other thread has the same priority as the current thread.
join() Method
join() is an instance method defined in the Thread class. When a calling thread calls join() on a target Thread instance, the calling thread blocks until the target thread completes execution, then the calling thread can resume.
Example usage of join():
public class JoinDemo {
static class WorkerThread extends Thread {
public WorkerThread(String name) {
super(name);
}
@Override
public void run() {
System.out.printf("%s start\n", this.getName());
// Simulate time-consuming work
for (long i = 0; i < 1_000_000; i++);
System.out.printf("%s finish\n", this.getName());
}
}
public static void main(String[] args) {
try {
WorkerThread t1 = new WorkerThread("t1");
t1.start();
// Main thread waits for t1 to complete
t1.join();
System.out.printf("%s finish\n", Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Output:
t1 start
t1 finish
main finish
Of the four methods discussed, the first three all operate on the current executing thread, regardless of which thread instance they are called on. Only join() blocks the calling thread to wait for the target thread (the method's owning instance) to finish execution.