I'm getting this error for the code below
First thread about to sleep
thread 1 run
Boolean assignment done.
Woke up and about to invoke wait()
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at IncorrectSynchronization$1.run(HelloWorld.java:23)
at java.lang.Thread.run(Thread.java:748)
When the Thread t1 is sleeping, I modified the lock to false from another thread. It then throws this IllegalMonitorStateException. It's still the same object, why would modifying the value cause IllegalMonitorStateException?
When I modify the lock to false from another thread inside a synchronized block, I no longer get that error. Can anyone explain the reason for what's happening under the hood?
public class HelloWorld{
public static void main( String args[] ) throws InterruptedException {
SampleTest.runExample();
}
}
class SampleTest{
Boolean flag = new Boolean(true);
public void example() throws InterruptedException {
Thread t0 = new Thread(new Runnable() {
public void run() {
synchronized (flag) {
try {
while (flag) {
System.out.println("First thread about to sleep");
Thread.sleep(2000);
System.out.println("Woke up and about to invoke wait()");
flag.wait();
System.out.println("wait() called");
}
} catch (InterruptedException ie) {
}
}
}
});
Thread t1 = new Thread(new Runnable() {
public void run() {
System.out.println("thread 1 run");
flag = false;
}
});
t0.start();
Thread.sleep(200);
t1.start();
t0.join();
t1.join();
}
public static void runExample() throws InterruptedException {
SampleTest test = new SampleTest();
test.example();
}
}
The problem is with this line:
flag = false;
This changes the reference of the flag
Boolean variable, from the original Boolean
object (which is created by the deprecated constructor which should not be used) to the pre-created Boolean.FALSE
instance (due to autoboxing). By the time the first thread calls flag.wait()
, the object is no longer the same as the one it used to synchronize, hence the IllegalMonitorStateException
.
In this scenario, it's much better to use an AtomicBoolean
and mutate its value in the other thread:
AtomicBoolean flag = new AtomicBoolean(true);
Now the second thread can update the value of the same object. It should also probably notify the first thread that is waiting on the object (like wait()
, notify()
also requires synchronizing on the object on which it is invoked):
Thread t1 = new Thread(new Runnable() {
public void run() {
synchronized(flag) {
System.out.println("thread 1 run");
flag.set(false);
flag.notify();
}
}
});