Skip to content

Instantly share code, notes, and snippets.

@jcttrll
Created January 3, 2020 02:28
Show Gist options
  • Save jcttrll/63ea65d0a7c96f3ca94fe9aa38c27f86 to your computer and use it in GitHub Desktop.
Save jcttrll/63ea65d0a7c96f3ca94fe9aa38c27f86 to your computer and use it in GitHub Desktop.
Java wait()/notify() example showing and explaining the use of a spinlock around the wait() call
public class WaitSpinLocksExample {
public static void main(String[] args) {
Thread thread1, thread2;
Base bad = new BadImplementationSubjectToSpuriousWakeup();
thread1 = new Thread(bad::print);
thread2 = new Thread(bad::calculate);
thread1.start();
thread2.start();
try {
// Wait for first (bad) example to complete before starting second (correct) example
thread1.join();
thread2.join();
} catch (InterruptedException e) {
assert false : "This simple program never interrupts threads; exception will not be encountered";
}
Base correct = new CorrectImplementationWithSpinlockAroundWait();
thread1 = new Thread(correct::print);
thread2 = new Thread(correct::calculate);
thread1.start();
thread2.start();
}
}
abstract class Base {
final Object lock = new Object();
int value;
boolean set;
abstract void print();
void calculate() {
synchronized (lock) {
System.out.print("Simulating some long-running calculation... ");
try {
Thread.sleep(3_000);
} catch (InterruptedException notPossible) {
assert false : "This simple program never interrupts threads; exception will not be encountered";
}
System.out.println("done");
value = 1_000 + (int) (Math.random() * 1_000_000);
set = true;
lock.notify();
}
}
}
class BadImplementationSubjectToSpuriousWakeup extends Base {
@Override
void print() {
synchronized (lock) {
try {
// FIXME: This is wrong; waiting outside a spinlock is subject to spurious wakeup -
// wait() may return without anyone calling notify()/notifyAll(). See
// https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/Object.html#wait(long,int)
//
// *Additionally*, this implementation is subject to a race condition! If, due to thread start order
// or thread scheduling intricacies, calculate() completes in its thread *before* the monitor is
// acquired by the synchronized block in this method, notify() will have *already been
// called* and we'll wait forever, here, since notify() is never called again.
lock.wait();
} catch (InterruptedException notPossible) {
assert false : "This simple program never interrupts threads; exception will not be encountered";
}
// This may be called before value is set, due to spurious wakeup (see bug above)
System.out.println("Value: " + value);
}
}
}
class CorrectImplementationWithSpinlockAroundWait extends Base {
@Override
void print() {
synchronized (lock) {
try {
while (!set) {
lock.wait();
}
} catch (InterruptedException notPossible) {
assert false : "This simple program never interrupts threads; exception will not be encountered";
}
System.out.println("Value: " + value);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment