在Java中wait、notify和notifyAll是如何工作的

Java中的多线程是一个非常复杂的问题,在写多线程的代码访问一个或多个共享资源的时候需要非常注意。Java5中引进了一些像BlockingQueue和Executor一样的类,移除了一些复杂的东西以便使用起来更加的简单。编程人员使用这些类的时候会比那些直接使用线程同步的编程人员感觉更好。和自己实现同步相比,我也推荐你使用这些新的API,但是很多时候我们也会因为各种各样的原因被要求这么做。当一些问题出现的时候掌握这些方法知识对你是非常有帮助的。在这篇文章中,我将会围绕着wait(),notify()和notifyAll()方法展开讨论。
什么是wait、notify和notifyAll方法
在我们讲这些方法之前,我们先来了解一些基本的定义。Java有三个final方法允许线程间关于资源的锁的状态进行交流。
wait:它告诉调用的线程放弃锁并去休眠,直到其他的线程进入相同的监视器并调用notify方法。wait方法释放锁优先于等待和重新获取锁优先于wait方法的返回。wait方法实际上和同步块是紧密相结合的,使用了一个同步机制的特征。换句话说,在Java中单纯的实现wait方法是不可能的,它是一个native。调用wait方法的通常语法像这样
synchronized( lockObject )
{
    while( ! condition )
    {
        lockObject.wait();
    }
     
    //take the action here;
}

notify:它唤醒一个调用了wait方法的对象。需要注意的是调用notify方法实际上并没有放弃资源锁,它告诉等待的线程可以醒来了。但是锁实际上并没有放弃直到唤醒者的同步块结束才可以。所以,如果一个唤醒者在一个资源上调用了notify但是唤醒者依然需要在资源上的同步块中执行10秒钟的动作,等待的线程依然需要等待至少10秒钟让唤醒者来释放这个对象的锁,即使notify这个方法已经被调用了。notify的一般的语法是这样的
synchronized(lockObject)
{
    //establish_the_condition;
 
    lockObject.notify();
     
    //any additional code if needed
}


notifyAll:它唤醒了在同一个对象上调用wait方法的所有线程,在大多数情况下,拥有最高优先级的线程会跑在第一位,但是也不是保证实现的,其他的和上边的notify方法是一样的。调用notifyAll方法的普通语法是这样的:
synchronized(lockObject)
{
    establish_the_condition;
 
    lockObject.notifyAll();
}


通常情况下,线程使用wait方法来确定条件的不存在,然后调用wait方法。当其他的线程建立了条件,它会调用notify方法,这个wait和notify机制没有指定一些特定条件或者变量的值。在调用之前由开发人员指定需要检查的条件。
目前为止,我们学到了一些你可能已经知道的基本知识。让我们写一些小的程序来理解一下怎么利用这些方法来直接得到我们想要的结果。
怎么使用wait、notify和notifyAll方法
在这个实践中,我们将使用wait和notify方法解决生产者和消费者问题。为了保证程序的简单并关于与wait和notify方法,我们将只包括一个生产者线程和一个消费者线程。程序的其他特点:
1、生产者线程每秒产生一个新的资源并把它放入到taskQueue中
2、消费者线程花费一秒的时间处理taskQueue中的资源
3、taskQueue中的最大的容量是5
4、两个线程无限制的运行
设计生产者线程
下边的代码是根据我们的需求设计的生产者线程
class Producer implements Runnable
{
   private final List<Integer> taskQueue;
   private final int           MAX_CAPACITY;
 
   public Producer(List<Integer> sharedQueue, int size)
   {
      this.taskQueue = sharedQueue;
      this.MAX_CAPACITY = size;
   }
 
   @Override
   public void run()
   {
      int counter = 0;
      while (true)
      {
         try
         {
            produce(counter++);
         }
         catch (InterruptedException ex)
         {
            ex.printStackTrace();
         }
      }
   }
 
   private void produce(int i) throws InterruptedException
   {
      synchronized (taskQueue)
      {
         while (taskQueue.size() == MAX_CAPACITY)
         {
            System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
            taskQueue.wait();
         }
           
         Thread.sleep(1000);
         taskQueue.add(i);
         System.out.println("Produced: " + i);
         taskQueue.notifyAll();
      }
   }
}


1、这里有一个produce(couter++)代码写在了无限循环中,这样就可以保证生产者按照一定的时间间隔持续的产生资源。
2、我们已经写了一个produce方法代码
3、一旦wait方法结束,生产者在taskQueue中增加一个元素并且调用notifyAll方法,因为最后一次wait方法的调用由消费者线程调用的,消费者获取这个信息。
4、消费者线程获取到这个消息之后,消费者会像逻辑中写的一样消费这个元素。
5、注意两个线程都使用了sleep方法,来假设创建和消耗资源时的时间消耗
设计消费者线程
下边的代码是根据我们的需求设计的消费者代码
class Consumer implements Runnable
{
   private final List<Integer> taskQueue;
 
   public Consumer(List<Integer> sharedQueue)
   {
      this.taskQueue = sharedQueue;
   }
 
   @Override
   public void run()
   {
      while (true)
      {
         try
         {
            consume();
         } catch (InterruptedException ex)
         {
            ex.printStackTrace();
         }
      }
   }
 
   private void consume() throws InterruptedException
   {
      synchronized (taskQueue)
      {
         while (taskQueue.isEmpty())
         {
            System.out.println("Queue is empty " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
            taskQueue.wait();
         }
         Thread.sleep(1000);
         int i = (Integer) taskQueue.remove(0);
         System.out.println("Consumed: " + i);
         taskQueue.notifyAll();
      }
   }
}


1、这里consume代码已经被写到一个无限循环中去了以此来保证在taskQueue中有元素时可以继续消费
2‘一旦wait方法结束,消费者移除taskQueue中的元素并调用notifyAll方法。因为最后一次调用wait方法是由生产者线程调用的,生产者线程得到信息。
3、生产者线程获得通知后,会根据已经写好的逻辑生产元素。
测试应用
我们来测试一下生产者和消费者线程
public class ProducerConsumerExampleWithWaitAndNotify
{
   public static void main(String[] args)
   {
      List<Integer> taskQueue = new ArrayList<Integer>();
      int MAX_CAPACITY = 5;
      Thread tProducer = new Thread(new Producer(taskQueue, MAX_CAPACITY), "Producer");
      Thread tConsumer = new Thread(new Consumer(taskQueue), "Consumer");
      tProducer.start();
      tConsumer.start();
   }
}
 
Output:
 
Produced: 0
Consumed: 0
Queue is empty Consumer is waiting , size: 0
Produced: 1
Produced: 2
Consumed: 1
Consumed: 2
Queue is empty Consumer is waiting , size: 0
Produced: 3
Produced: 4
Consumed: 3
Produced: 5
Consumed: 4
Produced: 6
Consumed: 5
Consumed: 6
Queue is empty Consumer is waiting , size: 0
Produced: 7
Consumed: 7
Queue is empty Consumer is waiting , size: 0


我建议你改变消费者和生产者的时候,看看不同的输出的结果
原文地址:http://howtodoinjava.com/2015/01/08/how-to-work-with-wait-notify-and-notifyall-in-java/

你可能感兴趣的:(wait,notify,notifyAll)