Thread Communication: wait( ) and notifyAll( )

Problem

The synchronized keyword lets you lock out multiple threads, but doesn’t give you much communication between them.

Solution

Use wait( ) and notifyAll( ). Very carefully.

Discussion

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( ).

notify( )

Causes a randomly selected thread waiting on this object to be awakened. It must then try to regain the monitor lock. If the “wrong” thread is awakened, your program can deadlock.

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.4.174