Search code examples
javamultithreadinglockingwaitsynchronized

Why am I getting IllegalMonitorStateException for the Thread t1


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();
    }
}

Solution

  • 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();
             }
           }
     });