Java|Java 使用 wait / notify / notifyAll 实现线程间通信

在 Java 中,可以通过配合调用 Object 对象的 wait() 方法和 notify() 方法或 notifyAll() 方法来实现线程间的通信。在线程中调用 wait() 方法,将阻塞等待其他线程的通知(其他线程调用 notify() 方法或 notifyAll() 方法),在线程中调用 notify() 方法或 notifyAll() 方法,将通知其他线程从 wait() 方法处返回。
Object 是所有类的超类,它有 5 个方法组成了等待 / 通知机制的核心:notify()、notifyAll()、wait()、wait(long) 和 wait(long, int)。在 Java 中,所有的类都从 Object 继承而来,因此,所有的类都拥有这些共有方法可供使用。而且,由于他们都被声明为 final,因此在子类中不能覆写任何一个方法。
wait()

public final void wait() throws InterruptedException, IllegalMonitorStateException
该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用 wait() 之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait() 方法。进入 wait() 方法后,当前线程释放锁。如果调用 wait() 时,没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException 的一个子类,因此,不需要 try-catch 结构。线程被唤醒后,会从上次调用 wait() 方法的地方继续执行。所以一般在循环中调用 wait() 方法,对于从 wait() 中被 notify() 的进程来说,它在被 notify() 之后还需要重新检查是否符合执行条件,如果不符合,就必须再次被 wait(),如果符合才能往下执行
notify()
public final native void notify() throws IllegalMonitorStateException
该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用 notify() 时没有持有适当的锁,也会抛出 IllegalMonitorStateException。
该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个 wait() 状态的线程来发出通知,并使它等待获取该对象的对象锁(notify 后,当前线程不会马上释放该对象锁,wait 所在的线程并不能马上获取该对象锁,要等到程序退出 synchronized 代码块后,当前线程才会释放锁,wait 所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象 notify 的线程们。当第一个获得了该对象锁的 wait 线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用 notify 语句,则即便该对象已经空闲,其他 wait 状态等待的线程由于没有得到该对象的通知,会继续阻塞在 wait 状态,直到这个对象发出一个 notify 或 notifyAll。这里需要注意:它们等待的是被 notify 或 notifyAll,而不是锁。这与下面的 notifyAll() 方法执行后的情况不同。
notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException
该方法与 notify() 方法的工作方式相同,重要的一点差异是:
notifyAll 使所有原来在该对象上 wait 的线程统统退出 wait 的状态(即全部被唤醒,不再等待 notify 或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll 线程退出调用了 notifyAll 的 synchronized 代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。
wait(long) 和 wait(long, int)
这两个方法是设置等待超时时间的,后者在超时时间上加上 ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从 wait(long) 返回。另外,需要知道,如果设置了超时时间,当 wait() 返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为 wait() 方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在 notify 之前改变标志位的值,在 wait() 方法后读取该标志位的值来判断,当然为了保证 notify 不被遗漏,我们还需要另外一个标志位来循环判断是否调用 wait() 方法。
生产者消费者实例说明 wait、notify、notifyAll 的使用方法
public class ProducerConsumerInJava { public static void main(String args[]) { Queue buffer = new LinkedList<>(); int maxSize = 10; Thread producer = new Producer(buffer, maxSize, "PRODUCER"); Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); producer.start(); consumer.start(); } } /** * Producer Thread will keep producing values for Consumer * to consumer. It will use wait() method when Queue is full * and use notify() method to send notification to Consumer * Thread. */ class Producer extends Thread { private Queue queue; private int maxSize; public Producer(Queue queue, int maxSize, String name) { super(name); this.queue = queue; this.maxSize = maxSize; } @Override public void run() { 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 (Exception ex) { ex.printStackTrace(); } } Random random = new Random(); int i = random.nextInt(); System.out.println("Producing value : " + i); queue.add(i); queue.notifyAll(); } } } } }/** * Consumer Thread will consumer values form shared queue. * It will also use wait() method to wait if queue is * empty. It will also use notify method to send * notification to producer thread after consuming values * from queue. */ class Consumer extends Thread { private Queue queue; private int maxSize; public Consumer(Queue queue, int maxSize, String name){ super(name); this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.isEmpty()) { System.out.println("Queue is empty, " + "consumer thread is waiting" + " for producer thread to put something in queue"); try { queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } System.out.println("Consuming value : " + queue.remove()); queue.notifyAll(); } } } } }

总结 如果线程调用了对象的 wait() 方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁
当有线程调用了对象的 notifyAll() 方法(唤醒所有 wait 线程)或 notify() 方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait() 方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁
永远在 synchronized 的函数或对象里使用 wait、notify 和 notifyAll,不然会抛出
IllegalMonitorStateException
永远在 while 循环里而不是 if 语句下使用 wait。这样,循环会在线程睡眠前后都检查 wait 的条件,并在条件实际上并未改变的情况下处理唤醒通知(用 if 判断的话,唤醒后线程会从 wait 之后的代码开始运行,但是不会重新判断 if 条件,直接继续运行 if 代码块之后的代码,而如果使用 while 的话,也会从 wait 之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行 while 代码块之后的代码块,成立的话继续 wait)
永远在多线程间共享的对象(在生产者消费者模型里即缓冲区队列)上使用 wait
【Java|Java 使用 wait / notify / notifyAll 实现线程间通信】一般倾向用 notifyAll(),而不是 notify()

    推荐阅读