The synchronized
keyword lets you lock out
multiple threads, but
doesn’t give you much communication between them.
Three methods appear in
java.lang.Object
that allow you to use any
object as a synchronization
target: wait( )
,
notify(
), and notifyAll( )
.
wait( )
Causes the current thread to block in the given object until awakened
by a notify( )
or notifyAll( )
.
notifyAll( )
Causes all threads waiting on the object to be awakened; each will then try to regain the monitor lock. Hopefully one will succeed.
The mechanism is a bit odd: there is no way to awaken only the thread
that owns the lock. However, that’s how it works, and
it’s the reason almost all programs use notifyAll( )
instead of notify( )
. Also note that
both wait( )
and the notification methods can be
used only if you are already synchronized on the
object; that is, you must be in a
synchronized method within, or a code block synchronized on, the
object that you wish your current thread to wait( )
or notify( )
upon.
For a
simple
introduction to wait( )
and notify( )
, I’ll use a simple Producer-Consumer model. This
pattern can be used to simulate a variety of real-world situations in
which one object is creating or allocating objects (producing them),
usually with a random delay, while another is grabbing the objects
and doing something with them (consuming them). A single-threaded
Producer-Consumer model is shown in Example 24-9. As
you can see, there are no threads created, so the entire
program -- the read( )
in
main
as well as produce( )
and
consume( )
-- runs in the same thread. You
control the production and consumption by entering a line consisting
of letters. Each p
causes one unit to be produced,
while each c
causes one unit to be consumed. So if
I run it and type pcpcpcpc
, the program will
alternate between producing and consuming. If I type
pppccc
, the program will produce three units and
then consume them. See Example 24-9.
Example 24-9. ProdCons1.java
public class ProdCons1 { /** Throughout the code, this is the object we synchronize on so this protected LinkedList list = new LinkedList( ); protected void produce( ) { int len = 0; synchronized(list) { Object justProduced = new Object( ); list.addFirst(justProduced); len = list.size( ); list.notifyAll( ); } System.out.println("List size now " + len); } protected void consume( ) { Object obj = null; int len = 0; synchronized(list) { while (list.size( ) == 0) { try { list.wait( ); } catch (InterruptedException ex) { return; } } obj = list.removeLast( ); len = list.size( ); } System.out.println("Consuming object " + obj); System.out.println("List size now " + len); } public static void main(String[] args) throws IOException { ProdCons1 pc = new ProdCons1( ); int i; while ((i = System.in.read( )) != -1) { char ch = (char)i; switch(ch) { case 'p': pc.produce( ); break; case 'c': pc.consume( ); break; } } } }
The part that may
seem strange is using
list
instead of the main class as the
synchronization target. Each object has its own wait queue, so it
does matter which object you use. In theory, any object can be used
as long as your synchronized
target and the object
in which you run wait()
and notify( )
are one and the same object. Of course, it is good to
refer to the object that you are protecting from concurrent updates,
so I used list
here.
Hopefully, you’re now wondering what this has to do with thread synchronization. There is only one thread, but the program seems to work:
> jikes +E -d . ProdCons1.java > java ProdCons1 pppccc List size now 1 List size now 2 List size now 3 Consuming object java.lang.Object@d9e6a356 List size now 2 Consuming object java.lang.Object@d9bea356 List size now 1 Consuming object java.lang.Object@d882a356 List size now 0
But this program is not quite right. If I enter even one more
c
’s than p
’s, think
about what happens. The consume( )
method does a
wait( )
, but it is no longer possible for the
read( )
to proceed. The program, we say, is
deadlocked
: it is waiting on something
that can never happen. Fortunately, this simple case is detected by
the Java runtime:
ppccc List size now 1 List size now 2 Consuming object java.lang.Object@18faf0 List size now 1 Consuming object java.lang.Object@15bc20 List size now 0 Dumping live threads: 'gc' tid 0x1a0010, status SUSPENDED flags DONTSTOP blocked@0x19c510 (0x1a0010->|) 'finaliser' tid 0x1ab010, status SUSPENDED flags DONTSTOP blocked@0x10e480 (0x1ab010->|) 'main' tid 0xe4050, status SUSPENDED flags NOSTACKALLOC blocked@0x13ba20 (0xe4050->|) Deadlock: all threads blocked on internal events Abort (core dumped)
Indeed, the read( )
is never executed, because
there’s no way for produce( )
to get called
and so the notify( )
can’t happen. To fix
this, I want to run the producer and the consumer in separate
threads. There are several ways to accomplish this. I’ll just
make consume( )
and produce( )
into
inner
classes Consume
and Produce
that extend Thread
, and their run( )
method will do the work of the previous methods. In the
process, I’ll replace the code that reads from the console with
code that causes both threads to loop for a certain number of
seconds, and change it to be a bit more of a simulation of a
distributed Producer-Consumer mechanism. The result of all this is
the second version,
ProdCons2
, shown in
Example 24-10.
Example 24-10. ProdCons2.java
import java.util.*; import java.io.*; public class ProdCons2 { /** Throughout the code, this is the object we synchronize on so this * is also the object we wait() and notifyAll( ) on. */ protected LinkedList list = new LinkedList( ); protected int MAX = 10; protected boolean done = false; // Also protected by lock on list. /** Inner class representing the Producer side */ class Producer extends Thread { public void run( ) { while (true) { Object justProduced = getRequestFromNetwork( ); // Get request from the network - outside the synch section. // We're simulating this actually reading from a client, and it // might have to wait for hours if the client is having coffee. synchronized(list) { while (list.size( ) == MAX) // queue "full" try { System.out.println("Producer WAITING"); list.wait( ); // Limit the size } catch (InterruptedException ex) { System.out.println("Producer INTERRUPTED"); } list.addFirst(justProduced); if (done) break; list.notifyAll( ); // must own the lock System.out.println("Produced 1; List size now " + list.size( )); // yield( ); } } } Object getRequestFromNetwork( ) { // Simulation of reading from client // try { // Thread.sleep(10); // simulate time passing during read // } catch (InterruptedException ex) { // System.out.println("Producer Read INTERRUPTED"); // } return(new Object( )); } } /** Inner class representing the Consumer side */ class Consumer extends Thread { public void run( ) { while (true) { Object obj = null; int len = 0; synchronized(list) { while (list.size( ) == 0) { try { System.out.println("CONSUMER WAITING"); list.wait( ); // must own the lock } catch (InterruptedException ex) { System.out.println("CONSUMER INTERRUPTED"); } } if (done) break; obj = list.removeLast( ); list.notifyAll( ); len = list.size( ); System.out.println("List size now " + len); } process(obj); // Outside synch section (could take time) //yield( ); } } void process(Object obj) { // Thread.sleep(xxx) // Simulate time passing System.out.println("Consuming object " + obj); } } ProdCons2(int nP, int nC) { for (int i=0; i<nP; i++) new Producer().start( ); for (int i=0; i<nC; i++) new Consumer().start( ); } public static void main(String[] args) throws IOException, InterruptedException { // Start producers and consumers int numProducers = 2; int numConsumers = 2; ProdCons2 pc = new ProdCons2(numProducers, numConsumers); // Let it run for, say, 30 seconds Thread.sleep(30*1000); // End of simulation - shut down gracefully synchronized(pc.list) { pc.done = true; pc.list.notifyAll( ); // Wake up any waiters! } } }
I’m happy to report that all is well with this. It will happily run for long periods of time, neither crashing nor deadlocking. After running for some time, I captured this tiny bit of the log:
Produced 1; List size now 118 Consuming object java.lang.Object@2119d0 List size now 117 Consuming object java.lang.Object@2119e0 List size now 116
By varying the number of producers and consumers started in the constructor method, you can observe different queue sizes that all seem to work correctly.
18.219.4.174