Created
January 3, 2020 02:28
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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