使线程执行的先后顺序notify and notifyAll(备份参考资料)

notify() and notifyAll() Java provides the wait(), notify(), and notifyAll() methods to facilitate event notification. The notify() and notifyAll() methods are used to wake up threads that are in a wait state. (Threads enter a wait state by calling the wait() method.)

The notify() method wakes up only one thread, regardless of how many threads are currently waiting. In addition, you cannot predict which waiting thread wakes up. This behavior is fine when only one thread is waiting or when you do not care which of the waiting threads is awakened. The problem with notify() comes when more than one thread is waiting and you need to predict or specify which waiting thread wakes up. The thread chosen by the JVM is out of your control.

By contrast, the notifyAll() method wakes up all waiting threads. After a call to notifyAll(), each waiting thread exits its wait state and competes for the lock. Using notifyAll() guarantees that if you have threads that are waiting on a condition, they all are awakened and all execute eventually. However, like notify(), you cannot predict or specify the order that these waiting threads wake up and execute.

When calling notify() or notifyAll(), the JVM does not necessarily choose a higher-priority thread over a lower-priority thread or the thread that has been waiting the longest when choosing which thread to wake up. In addition, the thread chosen by notify(), or the order of execution of threads after calling notifyAll(), might not be consistent on the same JVM for multiple executions of the same program. Therefore, you should never make assumptions about the order in which threads are awakened as a result of calls to these methods.

To demonstrate these issues, Listing 1 contains a program that uses five separate threads to represent five different robots. Each thread (robot) begins in a wait state, waiting for an array of data that contains all of the information required by the robot to do its work. When this array of data arrives, notify() is called. At this point, one of the five robot threads wakes up and does its work.

Listing 1. RobotController and Robot class using arbitrary notification with notify()


class Robot extends Thread
{
  private RobotController controller;
  private int robotID;

  public Robot(RobotController cntrl, int id)
  {
    controller = cntrl;
    robotID = id;
  }

  public void run()
  {
    synchronized(controller)
    {
      byte[] data;
      while ((data = controller.getData()) == null)
      {
        try {
          System.out.println("data is null...waiting " + robotID);
          controller.wait();                                      //1
        }
        catch (InterruptedException ie) {}
      }
      //Now we have data to move the robot
      System.out.println("Robot " + robotID + " Working");
    }
  }
}

class RobotController
{
  private byte[] robotData;
  private Robot rbot1;
  private Robot rbot2;
  private Robot rbot3;
  private Robot rbot4;
  private Robot rbot5;

  public static void main(String args[])
  {
    RobotController controller = new RobotController();
    controller.setup();
  }

  public void setup()
  {
    rbot1 = new Robot(this, 1);
    rbot2 = new Robot(this, 2);
    rbot3 = new Robot(this, 3);
    rbot4 = new Robot(this, 4);
    rbot5 = new Robot(this, 5);
    rbot3.setPriority(Thread.MAX_PRIORITY);                       //2
    rbot1.start();
    rbot2.start();
    rbot3.start();
    rbot4.start();
    rbot5.start();
    begin();
  }

  public void begin()
  {
    for (int i=0;i<5;i++)
    {
      try {
        Thread.sleep(500);
      }
      catch (InterruptedException ie){}
      putData(new byte[10]);
      synchronized(this){
        System.out.println("Calling notify");
        notify();                                                 //3
      }
    }
  }
  
  public byte[] getData()
  {
    if (robotData != null)
    {
      byte[] d = new byte[robotData.length];
      System.arraycopy(robotData, 0, d, 0, robotData.length);
      robotData = null;                                           
      return d;
    }
    return null;
  }

  public void putData(byte[] d)
  {
    robotData = d;
  }
}

 

This code contains two classes, the Robot class and the RobotController class. When the code is executed, the RobotController class creates five Robot objects. Each Robot object executes on a separate thread. Then, each of the threads is started and they all enter a wait state at //1 until data is provided they can operate on.

The code then enters the begin() method, which enters a loop that calls the notify() method five times. This process ensures that all five robots are eventually awakened. It first sleeps for 500 milliseconds before making an array of data available for a robot. (The sleep is performed to be sure all robots are initially in their wait state.) When data is available for a robot to operate on, notify() is called at //3 so one of the robots can begin operating on that data.

Executing the code in Listing 1 several times results in various output. Listings 2 and 3 contain sample output of two different executions of this program.

Listing 2. Output of program in Listing 1


data is null...waiting 3
data is null...waiting 1
data is null...waiting 2
data is null...waiting 4
data is null...waiting 5
calling notify
Robot 3 Working
calling notify
Robot 1 Working
calling notify
Robot 2 Working
calling notify
Robot 4 Working
calling notify
Robot 5 Working

 

Listing 3. Output of program in Listing 1


data is null...waiting 1
data is null...waiting 2
data is null...waiting 3
data is null...waiting 4
data is null...waiting 5
calling notify
Robot 1 Working
calling notify
Robot 2 Working
calling notify
Robot 3 Working
calling notify
Robot 4 Working
calling notify
Robot 5 Working

 

Notice that the order in which the waiting threads execute is different for various executions of the program. If you do not care the order in which the five robots wake up after each call to notify(), this code works fine. However, if you want to wake up one of the robots first, or simply want the robots to wake up in a particular order, you have a problem. Consider a situation where you need to control the order of the execution of the robots due to the data supplied. For example, assume that the robots are designed to handle different weights of the materials they handle. Then assume that the data provided contains information about how much weight the robot will move if it executes the commands contained in the array. Based on this information you may want to choose one robot in preference to another.

Suppose you want robot 3 to execute first when notify() is called because the data indicates that the weight of the material suits robot 3. Because there is no obvious programmatic way to do this, you might try to set thread 3Â’s priority to the maximum (see //2 in Listing 1). Robot 3 executes first most of the time using the IBM Java SDK, version 1.3; however, as the output in Listing 3 shows, there is no guarantee this will happen every time.

Even if you found a JVM that seemed to awaken the highest priority thread each time, you could not be sure this would always be the case because the Java Language Specification (JLS) clearly states that the order that waiting threads wake up is arbitrary. In addition, you still have no guarantees about other JVMs your code might run on.

You'll encounter similar problems with the behavior of the notifyAll() method. To modify the code from Listing 1 to use notifyAll(), change the following three things:

  1. Remove the for loop from the begin() method. You do not need the loop because calling notifyAll() releases all the threads.
  2. Replace the call to notify() at //3 with a call to notifyAll().
  3. Remove the robotData = null; statement from the getData() method. We assume for this case that the data contains the commands for all the robots, not just one.

With the call to notifyAll(), all the robots wake up and do their work. Again, if you want all the robots to wake up, but don't care the order in which they execute, this is fine. However, if you want to control the order in which these threads wake up, you have the same problem you had with notify(). The order in which these threads wake up is arbitrary.

I modified code in Listing 1 to use notifyAll(), and then executed the program several times. Note that the priority of thread 3 is still set to the maximum priority in an effort to have it execute first. On the majority of the executions, thread 3 executes first producing the output in Listing 4. However, further executions of the same program eventually produce the output in Listing 5.

Listing 4. Output of code from Listing 1 using notifyAll()


data is null...waiting 3 
data is null...waiting 1
data is null...waiting 2
data is null...waiting 4
data is null...waiting 5
Calling notifyAll
Robot 3 Working
Robot 1 Working
Robot 2 Working
Robot 4 Working
Robot 5 Working

 

Listing 5. Output of code from Listing 1 using notifyAll()


data is null...waiting 1
data is null...waiting 3
data is null...waiting 2
data is null...waiting 4
data is null...waiting 5
Calling notifyAll
Robot 1 Working
Robot 3 Working
Robot 2 Working
Robot 4 Working
Robot 5 Working

 

Listings 4 and 5 show that the order of the execution of waiting threads differs on various executions of the program. It also shows that on each call to notifyAll(), all waiting threads are awakened to compete for the lock.

We need a programmatic way to control which thread is executed as a result of a call to notify() or notifyAll(). Putting this decision in our hands, rather than at the whim of the JVM, means more control for us and more consistent and flexible implementations for our users.

Solving the problem with specific notification Controlling the order that waiting threads are awakened by a call to notify() or notifyAll() requires that the waiting threads synchronize on different objects. The threads in the code in Listing 1 all synchronize on the RobotController object. Whennotify() or notifyAll() is called, it is done so within a synchronized block for the RobotController object. Therefore, when all five threads are waiting and notify() is called inside the synchronized block for RobotController, one of the five threads is selected. When notifyAll() is called, all of the five waiting threads eventually execute, but the order they execute is out of your control.

Specific notification with notify() Controlling the order of execution of waiting threads requires that you create of a separate lock object for each thread, or set of threads, that you want to notify individually or together. Therefore, given the robot example in Listing 1, suppose you want to notify the threads in a particular order each time, based on the data provided. To do this, you would need to have each thread lock on a different object, then notify those objects in the order you wish their corresponding threads to execute. Consider the code in Listing 6, which is a modified version of the code from Listing 1 with specific notification facilities added.

Listing 6. Using specific notification with notify()

 
import java.util.*;                                              //1
class Robot extends Thread
{
  private RobotController controller;
  private int robotID;
  private byte[] lock;                                           //2

  public Robot(RobotController cntrl, int id)
  {
    controller = cntrl;
    robotID = id;
  }

  public byte[] getLock()                                        //3
  {                                                              //4
    return lock;                                                 //5
  }                                                              //6

  public void run()
  {
    lock = new byte[0];                                          //7
    synchronized(lock)                                           //8
    {
      byte[] data;
      while ((data = controller.getData()) == null)
      {
        try {
          System.out.println("Thread " + robotID + " waiting");
          lock.wait();                                           //9
        }
        catch (InterruptedException ie) {}
      }
      //Now we have data to move the robot
      System.out.println("Robot " + robotID + " Working");
    }
  }
}

class RobotController
{
  private byte[] robotData;
  private Vector threadList = new Vector();                      //10
  private Robot rbot1;
  private Robot rbot2;
  private Robot rbot3;
  private Robot rbot4;
  private Robot rbot5;

  public static void main(String args[])
  {
    RobotController controller = new RobotController();
    controller.setup();
  }

  public void setup()
  {
    rbot1 = new Robot(this, 1);
    rbot2 = new Robot(this, 2);
    rbot3 = new Robot(this, 3);
    rbot4 = new Robot(this, 4);
    rbot5 = new Robot(this, 5);
    threadList.addElement(rbot1);                                //11
    threadList.addElement(rbot2);                                //12
    threadList.addElement(rbot3);                                //13
    threadList.addElement(rbot4);                                //14
    threadList.addElement(rbot5);                                //15
    rbot3.setPriority(Thread.MAX_PRIORITY);
    rbot1.start();
    rbot2.start();
    rbot3.start();
    rbot4.start();
    rbot5.start();
    begin();
  }
  
  public void begin()
  {
    for (int i=4;i<=0;i--)                                    //16
    {
      try {
        Thread.sleep(500);
      }
      catch (InterruptedException ie){}
      putData(new byte[10]);

      Robot rbot = (Robot)threadList.elementAt(i);               //17
      byte[] robotLock = rbot.getLock();                         //18
      synchronized(robotLock) {                                  //19
        System.out.println("Calling notify");       
        robotLock.notify();                                      //20
      }
    }
  }

  public synchronized byte[] getData()                           //21
  {
    if (robotData != null)
    {
      byte[] d = new byte[robotData.length];
      System.arraycopy(robotData, 0, d, 0, robotData.length);
      robotData = null;
      return d;
    }
    return null;
  }

  public void putData(byte[] d)
  {
    robotData = d;
  }
}

 

The lines of code that changed from Listing 1 to Listing 6, in order to add specific notification facilities, are denoted by the comment lines //1 through //21. The Robot class adds a lock instance variable at //2 along with a getLock() method to return this value at //3 through //6. Inside the run() method, the lock variable is initialized to a zero element byte array at //7. This array serves as the lock object. Then, the synchronized block locks on the lock object at //8, and waits on the same object at //9. Because the lock is allocated inside the run() method, it ensures that each robot (thread) locks on a different object. Because the lock instance variable is initialized to a different object for each thread, the locks are all unique.

In the RobotController class, we create a Vector at //10 to store each of the threads that are created. The threads are added to the Vector at //11 through //15. The begin() method contains a for loop at //16 that notifies the threads in the reverse order of their creation. Inside the for loop a thread is removed from the Vector at //17. Then, at //18, the specific lock object that thread is using is queried using the getLock() method. The code then enters a synchronized block at //19 on the lock object and calls the lock object's notify() method at //20. Because there is only one thread synchronized on this particular lock object (each thread has its own lock), the call to notify() wakes up the only thread waiting, allowing you to programmatically choose the thread to wake up. By changing the loop, you can notify the threads in any order you wish.

The only other change is the getData() method at //21 must be declared synchronized to ensure that the robot threads do not execute this method concurrently. The code in Listing 1 did not have this problem because each thread was synchronizedon the RobotController object to which the getData() method belongs.

Executing the code in Listing 6 results, most often, in the output displayed in Listing 7. The order of notification is always the same; however, the order the threads enter their wait state is sometimes different. Most often, thread 3 enters its wait state first because that thread has the highest priority of the five threads. However, as Listing 8 shows, this is not always the case. What is important is that the threads always wake up in the order you specify in the code, which means that you have programmatic control over thread notification.

Listing 7. Output of code in Listing 6


Thread 3 waiting
Thread 1 waiting
Thread 2 waiting
Thread 4 waiting
Thread 5 waiting
Calling notify
Robot 5 Working
Calling notify
Robot 4 Working
Calling notify
Robot 3 Working
Calling notify
Robot 2 Working
Calling notify
Robot 1 Working

 

Listing 8. Output of code in Listing 6


Thread 1 waiting
Thread 2 waiting
Thread 3 waiting
Thread 4 waiting
Thread 5 waiting
Calling notify
Robot 5 Working
Calling notify
Robot 4 Working
Calling notify
Robot 3 Working
Calling notify
Robot 2 Working
Calling notify
Robot 1 Working

 

Specific notification with notifyAll() Listing 9 contains the code from Listing 6 modified to work with notifyAll(). The lines of code that changed in Listing 9, in order to use notifyAll() with specific notification, are denoted by the comment lines //1 through //16.

Listing 9. Using specific notification with notifyAll()


import java.util.*;
class Robot extends Thread
{
  private RobotController controller;
  private int robotID;
  private byte[] lock;

  public Robot(RobotController cntrl, int id, byte[] lockObj)    //1
  {
    controller = cntrl;
    robotID = id;
    if (lockObj == null)                                         //2
      lock = new byte[0];                                        //3
    else                                                         //4
      lock = lockObj;                                            //5
  }

  public byte[] getLock()
  {
    return lock;
  }

  public void run()
  {                            
    synchronized(lock)
    {
      byte[] data;
      while ((data = controller.getData()) == null)
      {
        try {
          System.out.println("Thread " + robotID + " waiting");
          lock.wait();
        }
        catch (InterruptedException ie) {}
      }
      //Now we have data to move the robot
      System.out.println("Robot " + robotID + " Working");
    }
  }
}

class RobotController
{
  private byte[] robotData;
  private Vector threadList = new Vector();
  private Robot rbot1;
  private Robot rbot2;
  private Robot rbot3;
  private Robot rbot4;
  private Robot rbot5;
  private byte[] lock1;                                          //6
  private byte[] lock2;                                          //7

  public static void main(String args[])
  {
    RobotController controller = new RobotController();
    controller.setup();
  }

  public void setup()
  {
    lock1 = new byte[0];                                         //8
    lock2 = new byte[0];                                         //9
    rbot1 = new Robot(this, 1, lock1);                           //10
    rbot2 = new Robot(this, 2, lock2);                           //11
    rbot3 = new Robot(this, 3, null);                            //12
    rbot4 = new Robot(this, 4, lock1);                           //13
    rbot5 = new Robot(this, 5, lock2);                           //14
    threadList.addElement(rbot1);
    threadList.addElement(rbot2);
    threadList.addElement(rbot3);
    threadList.addElement(rbot4);
    threadList.addElement(rbot5);
    rbot3.setPriority(Thread.MAX_PRIORITY);
    rbot1.start();
    rbot2.start();
    rbot3.start();
    rbot4.start();
    rbot5.start();
    begin();
  }
  
  public void begin()
  {
    for (int i=0;i<3;i++)                                        //15
    {
      try {
        Thread.sleep(500);
      }
      catch (InterruptedException ie){}
      putData(new byte[10]);

      Robot rbot = (Robot)threadList.elementAt(i);
      byte[] robotLock = rbot.getLock();
      synchronized(robotLock) {
        System.out.println("Calling notifyAll");  
        robotLock.notifyAll();                                   //16 
      }
    }
    System.exit(0);
  }

  public synchronized byte[] getData()
  {
    if (robotData != null)
    {
      byte[] d = new byte[robotData.length];
      System.arraycopy(robotData, 0, d, 0, robotData.length);
      return d;
    }
    return null;
  }

  public void putData(byte[] d)
  {
    robotData = d;
  }
}

 

The Robot class constructor, changed in //1 through //5, now allows you to specify a unique lock object per robot (thread), or have multiple threads with the same lock object. If you group threads with the same lock object, a call to notifyAll() will arbitrarily wake up the threads in this group.

The RobotController class is changed to create and store two lock objects at //6 through //9. At //10 through //14 the five robot threads are created. Robots 1 and 4 share lock object lock1. Robots 2 and 5 share lock object lock2. Robot 3 does not specify a lock object; therefore, it gets a unique lock object in the Robot constructor at //3. The rationale for this code sharing locks between threads is as follows: You want to control the order the threads awaken but you do not care if thread 1 or thread 4 executes before the other, only that you can control their execution in relation to the other threads. This also applies to thread 2 and thread 5. Thread 3 has its own lock, inferring you want to control when it wakes up in relation to all other threads.

There are now three lock objects and five threads. Two threads belong to the first lock object, two threads belong to the second, and one thread to the third. This allows you to programmatically notify these threads in an orderly fashion. In the begin() method at //15, the code loops through the three lock objects notifying them in order. First, lock1 is notified with a call to notifyAll() at //16. This step has the effect of waking up threads 1 and 4. Note that you do not control which of these two threads executes first, but you have specified that either thread 1 or thread 4 executes before any of the other three threads. If you want finer control, you simply create a separate lock object for both thread 1 and 4 and notify that object separately as we do with thread 3.

Next, notifyAll() is called on lock2 waking up thread 2 and thread 5. Finally, thread 3 is awakened by the last call to notifyAll(). Sample output of the code in Listing 9 is contained in Listing 10.

Listing 10. Output of code in Listing 9


Thread 3 waiting
Thread 1 waiting
Thread 2 waiting
Thread 4 waiting
Thread 5 waiting
Calling notifyAll
Robot 1 Working
Robot 4 Working
Calling notifyAll
Robot 2 Working
Robot 5 Working
Calling notifyAll
Robot 3 Working

 

This output demonstrates that, using specific notification and notifyAll(), you can control the order of execution of sets of waiting threads. If you wanted all five threads to execute in a predetermined order, you would simply use notify() with the techniques used in Listing 6. Use notifyAll() only when you want to group threads together for notification purposes.

Summary There are times when you need to specify the order of execution of waiting threads. The Java platform does not support this need inherently. You can create a programmatic mechanism to control the order of execution of waiting threads by implementing the Specific Notification Pattern discussed in this article. This technique entails creating a separate lock object for each thread, or set of threads, you want to notify. When you have more than one lock object per thread, you use notifyAll() to awaken them. If each thread has its own lock object, you use notify().

Adding support for specific notification gives you direct control over the execution of waiting threads -- with little cost to the program in terms of complexity or execution speed.

posted @ 2013-03-20 14:07  IamThat  阅读(420)  评论(0编辑  收藏  举报