Java Concurrency: Object monitor methods

Previously we used monitor locks to solve a problem in a thread safe way. On Java each object has a lock associated with it. Among with this monitor lock the object has some methods that depend on it: wait, notify, notifyAll. Those methods can help on coordinating threads. For example a thread writing to a variable and notifying another thread on that variable change.

By using the wait method the thread will wait until notified or interrupted. In order to call this method from an object the thread should have acquired the lock associated with the object. Behind the scenes the thread will get into a wait queue among other threads that wait. That queue is associated with the object that we use its monitor lock.

We can see that on the objectMonitor sourcecode.

  // create a node to be put into the queue
  // Critically, after we reset() the event but prior to park(), we must check
  // for a pending interrupt.
  ObjectWaiter node(current);
  node.TState = ObjectWaiter::TS_WAIT;
  current->_ParkEvent->reset();
  OrderAccess::fence();          

Once the thread gets into a wait state, it will relinquish the lock it acquired from the object. The thread will be dormant until it is interrupted or it gets notified to wake up.

We shall use an object as a lock and provide a runnable implementation:

    private Object object = new Object();

    public class WaitingRunnable implements Runnable {

        @Override
        public void run() {
            try {
                log.info("Waiting to be notified");
                synchronized (object) {
                    object.wait();
                    log.info("Notified!");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

If we try to call wait on the Object without acquiring the monitor lock a failure will occur.

    @Test
    void illegalMonitorState() {
        assertThrows(IllegalMonitorStateException.class, () -> object.wait());
    }

Instead we will be successful if we acquire the lock.

    @Test
    void testNotifyWait() throws InterruptedException {
        Thread waitingThread = new Thread(new WaitingRunnable());
        waitingThread.start();

        Thread.sleep(500l);
        synchronized (object) {
            log.info("Lock acquired");
            object.notify();
            Thread.sleep(1000);
            log.info("Not released yet");
        }

        waitingThread.join();
    }

We can see the following output

21:40:23.606 [Thread-0] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Waiting to be notified
21:40:24.111 [main] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Lock acquired
21:40:25.117 [main] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Not released yet
21:40:25.121 [Thread-0] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Notified!

Let’s take this into one step further. Once the thread is awaken it will try to acquire the lock again. The lock will not be released until the thread that called notify will exit.
We can see that in the following example:

    @Test
    void testNotifyWait() throws InterruptedException {
        Thread waitingThread = new Thread(new WaitingRunnable());
        waitingThread.start();

        Thread.sleep(500l);
        synchronized (object) {
            log.info("Lock acquired");
            object.notify();
            log.info("Should wait");
            Thread.sleep(1000);
        }

        waitingThread.join();
    }

The output would be the following:

21:38:38.431 [Thread-0] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Waiting to be notified
21:38:38.936 [main] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Lock acquired
21:38:38.936 [main] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Should wait
21:38:39.942 [Thread-0] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Notified!

We have the notify method and the notifyAll method. In case of notify will pick one thread from the waiting queue and notify it. In case of notifyAll all threads from the queue will be notified.

For example:

    @Test
    void testNotifyOnce() throws InterruptedException {
        Thread firstThread = new Thread(new WaitingRunnable());
        Thread secondThread = new Thread(new WaitingRunnable());

        firstThread.start();
        secondThread.start();

        Thread.sleep(500l);
        synchronized (object) {
            log.info("Lock acquired");
            object.notify();
        }

        firstThread.join();
        secondThread.join(2000);
    }

Only one thread will be notified which we can see from the output

07:52:28.095 [Thread-1] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Waiting to be notified
07:52:28.095 [Thread-0] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Waiting to be notified
07:52:28.600 [main] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Lock acquired
07:52:28.601 [Thread-1] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Notified!

Now if we use notify all, all waiting threads will be notified :

    @Test
    void testNotifyAll() throws InterruptedException {
        Thread firstThread = new Thread(new WaitingRunnable());
        Thread secondThread = new Thread(new WaitingRunnable());

        firstThread.start();
        secondThread.start();

        Thread.sleep(500l);
        synchronized (object) {
            log.info("Lock acquired");
            object.notifyAll();
        }

        firstThread.join();
        secondThread.join();
    }

07:54:28.712 [Thread-1] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Waiting to be notified
07:54:28.712 [Thread-0] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Waiting to be notified
07:54:29.211 [main] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Lock acquired
07:54:29.211 [Thread-1] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Notified!
07:54:29.212 [Thread-0] INFO com.gkatzioura.concurrency.monitor.MonitorTest - Notified!

That’s it! We went one step further into coordinating threads by using the monitor method provided by the Object class.

Advertisement