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.