Java implements monitors, but with a single queue. The queue is ``anonymous'' (but is really implicitly named by the object in which it is defined, as we shall see).
Any class definition can be augmented to include monitor like behaviour. We add a qualifier synchronized to any method that requires exclusive access to the instance variables of the class. Thus, if we have two or more functions that are declared to be synchronized and one of them is currently being executed, any attempt to enter another synchronized method will be blocked and force the thread into the external queue. In Java terminology, there is a lock associated with the object. Only one thread can own the lock at one time and only the thread that owns the lock can execute a synchronized method. At the end of the synchronized method, the thread relinquishes the lock.
Inside a synchronized method, the wait() statement behaves like the normal wait() statement in a monitor, except that there is only one (unnamed) queue that the process can wait on. The corresponding wakeup call should be called notify() but in Java, for some obscure reason, there are two functions notify() and notifyAll(). The function notify() signals only one of the waiting processes, at random. Instead, notifyAll() signals all waiting processes (as one would expect) and this should be used by default.
Java monitors use the signal and continue mechanism, so the notifying process can continue execution after sending a wakeup signal and the processes that are woken up move to the external queue and are in contention to grab the object lock when it next becomes available.
The Java implementation of the bank account class looks pretty much like the monitor description given earlier. One difference is that it can include non-synchronized methods, which do not require having control of the object lock.
public class bank_account{ double accounts[100]; // transfer "amount" from accounts[source] to accounts[target] public synchronized boolean transfer (double amount, int source, int target){ while (accounts[source] < amount){ wait(); } accounts[source] -= amount; accounts[target] += amount; notifyAll(); return true; } // compute the total balance across all accounts public synchronized double audit(){ double balance = 0.0; for (int i = 0; i < 100; i++){ balance += accounts[i]; } return balance; } // a non-synchronized method public double current_balance(int i){ return accounts[i]; } }
In addition to synchronized methods, Java has a slightly more flexible mechanism. Every object has a lock, so an arbitrary block of code can be synchronized with respect to any object, as follows:
public class XYZ{ Object o = new Object(); public int f(){ .. synchronized(o){ ... } } public double g(){ .. synchronized(o){ ... } }
Now, f() and g() can both begin to execute in parallel in different threads, but only one thread at a time can grab the lock associated with o and enter the block enclosed by synchronized(o). The other will be placed in the equivalent of an ``external queue'' for the object o, while waiting for the object lock.
In addition, there is a separate ``internal queue'' associated with o, so one can write.
public class XYZ{ Object o = new Object(); public int f(){ .. synchronized(o){ ... o.wait(); // Wait in the internal queue attached to "o" ... } } public double g(){ .. synchronized(o){ ... o.notifyAll(); // Wake up the internal queue attached to "o" ... } }
Notice that we can ``rewrite'' completely a synchronized method of the form
public synchronized double h(){ ... }
as an externally unsynchronized method that is internally synchronized on this as follows:
public double h(){ synchronized(this){ ... } }
Also, the ``anonymous'' calls wait() and notify()/notifyAll() are actually the normal object-oriented abbreviations for this.wait() and this.notify()/this.notifyAll().
Actually, wait() throws an exception InterruptedException (that we shall examine when we look at how to define threads in Java), so more correctly we should encapsulate calls to wait() (or o.wait()) as follows:
try{ wait(); } catch (InterruptedException e);
Also, it is a mistake to use wait() or notify()/notifyAll() other than in a synchronized method. This will throw an IllegalMonitorStateException. Similarly, it is an error to to use o.wait(), o.notify() or o.notifyAll() other than within a block synchronized on o.