在 Java 中,wait、notify 和 notifyAll 通常用来实现线程间的通信。如果一个线程想要告诉另一个线程某些事情,就可以使用java.lang.Object中的notify()notifyAll()方法。wait()和notify()方法的典型实例就是经典的生产者-消费者设计模式,一条线程即生产者生产数据并将数据放入共享的“篮子”中,那么生产者可以通知另一条线程即消费者,让消费者开始消耗数据,因为队列缓冲区即“篮子”中有内容待消费(不为空)。相应的,消费者可以通知生产者可以开始生成更多的数据,因为当它消耗掉某些数据后缓冲区不再为满。

wait()

java.lang.Object中有三种不同参数的wait()方法。一种是无参方法,表示让线程一直等待下去直到调用了notify()或者notifyAll()方法。另外两种则需要给定一定的时间,在既定时间内线程将会是等待状态。

notify

notify()方法只会唤醒一个在等待状态下的线程,因此如果有多个线程等待一个对象,这个方法将唤醒他们中的一个。唤醒的线程的选择取决于线程管理的操作系统。

notifyAll()

notifyAll方法唤醒所有等待的线程对象,虽然这一进程将首先取决于操作系统的实现。

让我们看一个例子,多个线程对同一对象,我们使用wait(),notify()和notifyAll()方法。

step 1:首先是一个java bean类

Message.java :

public class Message {
    private String msg;

    public Message(String str){
        this.msg=str;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String str) {
        this.msg=str;
    }

}

step 2:接着是等待线程类

Waiter.java :

public class Waiter implements Runnable{
    private Message msg;

    public Waiter(Message msg) {
        this.msg = msg;
        // TODO Auto-generated constructor stub
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        //获得线程名称
        String name = Thread.currentThread().getName();
        synchronized (msg) {
            System.out.println(name + " waiting to get notified at time:"
                    + System.currentTimeMillis());
            try {
                msg.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        System.out.println(name+" waiter thread got notified at time:"
        +System.currentTimeMillis());

        System.out.println(name+" processed: "+msg.getMsg());
    }
}

step 3:然后是通知线程类

Notifier.java :

public class Notifier implements Runnable{
    private Message msg;    

    public Notifier(Message m) {
        this.msg = m;
        // TODO Auto-generated constructor stub
    }
    @Override
        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println(name+" started");
            try {
                Thread.sleep(1000);
                synchronized (msg) {
                    msg.setMsg(name+" Notifier work done");
                    msg.notify();
                  //msg.notifyAll();
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }

step 4:最后是测试类

WaiterNotifierTest.java :

public class WaiterNotifierTest {
    public static void main(String[] args) {
        Message msg = new Message("process it");

        Waiter waiter = new Waiter(msg);
        Thread st1 = new Thread(waiter,"waiter");
        st1.start();


        Waiter waiter1 = new Waiter(msg);
        Thread st11 = new Thread(waiter1,"waiter1");
        st11.start();

        Notifier notifier = new Notifier(msg);
        Thread st2 = new Thread(notifier,"notifier");       
        st2.start();

        System.out.println("All the threads are started");

    }
}

当我们将调用上面的程序,我们会看到下面的输出,程序有两个线程等待消息对象,调用notify()方法之后其中之一的线程waiter1已经醒来,其他线程仍然等待通知。

waiter1 waiting to get notified at time:1476096659142
waiter waiting to get notified at time:1476096659143
All the threads are started
notifier started
waiter1 waiter thread got notified at time:1476096660145
waiter1 processed: notifier Notifier work done

如果我们调用notifyall()通知类,下面将产生的输出:

waiter waiting to get notified at time:1476096862914
waiter1 waiting to get notified at time:1476096862915
All the threads are started
notifier started
waiter1 waiter thread got notified at time:1476096863917
waiter waiter thread got notified at time:1476096863917
waiter1 processed: notifier Notifier work done
waiter processed: notifier Notifier work done

可以看到两条程序都被唤醒。

如何使用Wait

从上面的Waiter类中,我们需要注意两点:

  • 第一个问题就是,我们怎么在代码里使用wait()呢?因为wait()并不是Thread类下的函数,我们并不能使用Thread.call()。事实上很多Java程序员都喜欢这么写,因为它们习惯了使用Thread.sleep(),所以他们会试图使用Thread.wait()来达成相同的目的,但很快他们就会发现这并不能顺利解决问题。正确的方法是对在多线程间共享的那个Object来使用wait。在生产者消费者问题中,这个共享的Object就是那个缓冲区队列,在本例中就是Message对象。
  • 第二个问题是,既然我们应该在synchronized的函数或是对象里调用wait,那哪个对象应该被synchronized呢?答案是,那个你希望上锁的对象就应该被synchronized,即那个在多个线程间被共享的对象。在生产者消费者问题中,应该被synchronized的就是那个缓冲区队列,在本例中就是Message对象。

永远在循环(loop)里调用 wait 和 notify,不是在 If 语句

现在你知道wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在while循环,而不是if语句中调用wait。因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者开始小号数据。

基于以上认知,下面这个是使用wait和notify函数的规范代码模板:

synchronized (sharedObject) { 
    while (condition) { 
    sharedObject.wait(); 
        // (Releases lock, and reacquires on wakeup) 
    } 
    // do action based upon condition e.g. take or put into queue 
}

就像我之前说的一样,在while循环里使用wait的目的,是在线程被唤醒的前后都持续检查条件是否被满足。如果条件并未改变,wait被调用之前notify的唤醒通知就来了,那么这个线程并不能保证被唤醒,有可能会导致死锁问题。

生产者-消费者模型

在上面的例子中我们只是简单的了解了wait()和notify()以及notifyAll()的基本用法。接下来我们将进入经典的生产者消费者模型来认识wait()和notify()方法。

那么什么事生产者-消费者模型呢?举个现实生活中的例子吧。李小明爸爸老王的苹果园丰收了。老王往篮子里放苹果,篮子只能装得下10个苹果,规定只有当篮子里的苹果装满的时候,小明才可以开始吃苹果了。当小明吃完篮子里的苹果时,老王才会往篮子里放苹果。这个其实就是多线程同步的问题。

我们有两个线程,分别名为Producer(生产者)和Consumer(消费者),他们分别继承了Producer和Consumer类,而Producer和Consumer都继承了Thread类。Producer和Consumer想要实现的代码逻辑都在run()函数内。Main线程开始了生产者和消费者线程,并声明了一个LinkedList作为缓冲区队列(在Java中,LinkedList实现了队列的接口)。生产者在无限循环中持续往LinkedList里插入随机整数直到LinkedList满。我们在while(queue.size == maxSize)循环语句中检查这个条件。请注意到我们在做这个检查条件之前已经在队列对象上使用了synchronized关键词,因而其它线程不能在我们检查条件时改变这个队列。如果队列满了,那么Producer线程会在Consumer线程消耗掉队列里的任意一个整数,并用notify来通知Producer线程之前持续等待。在我们的例子中,wait和notify都是使用在同一个共享对象上的。

生产者(Producer):

Producer.java :

public class Producer extends Thread{
    private final Queue queue;
    private int maxSize;
    public Producer(Queue queue,int maxSize,String name) {
        super("Producer");
        this.maxSize = maxSize;
        this.queue = queue; 
        // TODO Auto-generated constructor stub
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            synchronized (queue) {
                while(queue.size()==maxSize){               

                     try {
                         System.out .println("Queue is full, " + 
                                    "Producer thread waiting for " + 
                                    "consumer to take something from queue"); 
                        queue.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                 Random random = new Random();
                 int i = random.nextInt();
                 System.out.println("Producer value : "+i);
                 queue.add(i);
                 queue.notifyAll();

                }
            }
        }
    }

消费者(Constomer):

Constomer.java :

public class Customer extends Thread{
    private Queue queue;
    private int maxSize;
    public Customer(Queue queue,int maxSize,String name) {
        super("Customer");
        this.maxSize = maxSize;
        this.queue = queue;
        // TODO Auto-generated constructor stub
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            synchronized (queue) {
                while(queue.size()==0){

                    try {
                        System.out.println("Queue is empty," + 
                                "Consumer thread is waiting" +
                                " for producer thread to put something in queue"); 
                        queue.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                  int number = (int) queue.poll();
                  System.out.println("Consuming value : " + queue.remove());
                  queue.notifyAll();

            }
        }
    }

}

测试类(ProducerCustomerTest):

ProducerCustomerTest.java :

public class ProducerCustomerTest {

    public static void main(String[] args) {
         System.out.println("How to use wait and notify method in Java"); 
         System.out.println("Solving Producer Consumper Problem"); 
         Queue buffer = new LinkedList(); 
         int maxSize =10;
         Thread producer = new Producer(buffer,maxSize,"Producer");
         Thread customer = new Customer(buffer,maxSize,"Customer");      
         producer.start();
         customer.start();
    }

}

输出结果如下:

java中notify能释放锁吗 java的notifyall_java

生产者将不停地生产数据,消费者也将不停地消耗数据。

这就是在这个简单的例子中使用java线程间通信wait和notify方法。你可以看到,生产者和消费者线程彼此交流和使用共享队列的数据共享。这是生产者消费者设计模式以及经典的例子,它本身涉及的线程间的通信和数据在java线程之间共享。