Java线程间协作wait、notify和notifyAll详解
概要描述
在 Java 中可以用 wait、notify 和 notifyAll 来实现线程间的通信。尽管关于wait和notify的概念很基础,它们也都是Object类的函数,但用它们来写代码却并不简单。
wait, notify, notifyAll 都是基类Object的方法,而不属于Thread,这让习惯了调用Thread.sleep()使线程阻塞的同学感到奇怪。不过这样设计是有道理的,因为这些方法操作的锁(monitor)也是对象的一部分。可见,与sleep不同,通过调用共享对象的wait方法使当前线程等待;通过调用对象的notify, notifyAll 方法唤醒该对象上的等待线程。
先来看官方文档,Java doc对wait方法的描述:
public final void wait() throws InterruptedException Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0). The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution. As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop: synchronized (obj) { while (<condition does not hold>) obj.wait(); ... // Perform action appropriate to condition } This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor. Throws: IllegalMonitorStateException - if the current thread is not the owner of the object's monitor. InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown.
Java doc对notify的描述:
public final void notify() Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods. The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object. This method should only be called by a thread that is the owner of this object's monitor. A thread becomes the owner of the object's monitor in one of three ways: By executing a synchronized instance method of that object. By executing the body of a synchronized statement that synchronizes on the object. For objects of type Class, by executing a synchronized static method of that class. Only one thread at a time can own an object's monitor. Throws: IllegalMonitorStateException - if the current thread is not the owner of this object's monitor.
生产者-消费者示例:
/** * */ public class Producer extends Thread { private volatile Queue<Integer> queue; private int maxSize; public Producer(Queue<Integer> queue, int maxSize) { this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { //wait,notify方法必须在同步代码中运行 synchronized (queue) { //条件一定在循环中判断,以防死锁 while (queue.size() == maxSize) { try { System.out.println(Thread.currentThread().getName() + " wait."); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } int i = new Random().nextInt(); System.out.println(Thread.currentThread().getName() + " produce: " + i); queue.add(i); queue.notifyAll(); } } } }
消费者:
/** * */ public class Consumer extends Thread { private volatile Queue<Integer> queue; private int maxSize; public Consumer(Queue<Integer> queue, int maxSize) { this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.isEmpty()) { try { System.out.println(Thread.currentThread().getName() + " wait."); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " consume: " + queue.remove()); queue.notifyAll(); } } } }
main方法:
/** * */ public class WaitNotifyMain { private volatile static Queue<Integer> queue = new LinkedList<>(); /** * @param args */ public static void main(String[] args) { Producer producer = new Producer(queue, 10); producer.setName("Producer");//设置线程名称 Consumer consumer = new Consumer(queue, 10); consumer.setName("Consumer"); producer.start(); consumer.start(); } }
打印输出:
Producer produce: -2017386252
Producer produce: 1186339917
Producer produce: -674757828
Producer produce: 605757848
Producer produce: -539314860
Producer produce: 490590935
Producer produce: -845855520
Producer produce: -1459720588
Producer produce: 1274488529
Producer produce: 55225134
Producer wait.
Consumer consume: -2017386252
Consumer consume: 1186339917
Consumer consume: -674757828
Consumer consume: 605757848
Consumer consume: -539314860
Consumer consume: 490590935
Consumer consume: -845855520
Consumer consume: -1459720588
Consumer consume: 1274488529
Consumer consume: 55225134
Consumer wait.
Producer produce: 673397316
Producer produce: -1176693368
Producer produce: -1707265532
Producer produce: -1614197913
Producer produce: 306171031
Producer produce: -1646438955
Producer produce: 1141572321
Producer produce: 1235215288
Producer produce: -692805724
Producer produce: -2131184778
Producer wait.
Consumer consume: 673397316
Consumer consume: -1176693368
总结
wait, notify, notifyAll 是共享对象上的调用,而不是线程对象的调用。
wait, notify, notifyAll一定要在共享对象同步方法或同步代码块中执行,否则会在运行时抛出IllegalMonitorStateException的异常。因为wait, notify, notifyAll包含了对共享对象锁的操作,所以之前一定要先synchronized获取对象锁。
在共享对象上调用wait()时,当前线程进入等待状态, 并释放刚获取的对象锁(Thread.sleep()是不释放锁的),让出CPU, 此时,其他线程可以调用共享对象的同步方法或代码块。
唤醒线程在共享对象上执行notify会随机唤醒该对象的其中之一等待线程;唤醒线程在共享对象上执行notifyAll会唤醒该对象上的所有等待线程;这里要着重注意两点:
A,唤醒线程执行完共享对象的notify或notifyAll方法后,仍然要执行完synchronized修饰的同步代码块中后面的代码才能释放对象锁,因此,通常notify后面尽量减少执行代码,让对象锁尽快释放。
B, 唤醒是指线程ready的状态,尚未运行,共享对象的锁被唤醒线程释放后,ready状态的线程跟普通线程一样需要竞争共享对象的锁,执行同步代码块中wait()后面的代码。
永远在循环(loop)里调用 wait 和 notify, notifyAll,不是在 If 语句,避免死锁情况发生。
基于以上认知,下面这个是使用wait和notify函数的规范代码模板:
// The standard idiom for calling the wait method in Java 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的唤醒通知就来了,那么这个线程并不能保证被唤醒,有可能会导致死锁问题。
到此这篇关于Java线程间协作wait、notify和notifyAll详解的文章就介绍到这了,更多相关Java线程间协作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
教你开发脚手架集成Spring Boot Actuator监控的详细过程
这篇文章主要介绍了开发脚手架集成Spring Boot Actuator监控的详细过程,集成包括引入依赖配置文件及访问验证的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2022-05-05
最新评论