Wait和Notify是Java最基础的线程同步机制。在该同步机制中,通过使用Wait和Notify两类操作搭建起保护代码区间(Guarded Block),以使得程序正确运行,避免竞态条件发生。
Wait/Notify机制由Object类中的五个成员方法组成。调用Wait方法后,线程将进入等待状态,直到线程被唤醒。而调用Notify方法会唤醒等待该对象的线程。
Wait方法有下面三种形式。调用第一个wait()方法会将当前线程进入等待状态,直到被唤醒。如果没有被通知的话,该线程将永远等待下去。而第二个和第三个wait()方法允许调用者设置一个最长等待时间,如果在该时间段内未被唤醒的话,wait()方法立即返回,当前线程可以继续执行下一条语句。
public class Object {
...
public void wait();
public void wait(long timeout);
public void wait(long timeout, int nanos);
...
}
Notify方法有两种形式。notify()方法会唤醒某一个等待该对象的线程。哪一个线程被唤醒由Java的具体实现决定。Java语言给与了充分的自由。notifyAll()方法则唤醒所有等待该对象的线程。
public class Object {
...
public void notify();
public void notifyAll();
...
}
Wait/Notify使用方法非常复杂,有许多实现细节需要考虑。有时,Wait/Notify机制被人们称为Java多线程的“汇编语言”。因此,小水滴并不建议读者使用Wait/Notify机制实现线程同步。绝大多数Java多线程程序是由其他同步机制或者使用标准库实现的。但是,本系列文章还是收录了Wait/Notify,这是因为Wait/Notify是一种最为基础的同步方式。理解Wait/Notify机制有助于帮助理解Java多线程编程的复杂性和增强分析问题的能力。
在使用上,Wait/Notify机制有如下要求和细节。
最后,我们用一个生产者/消费者实例来展示wait/notify机制的使用方法。使用其他机制实现生产者/消费者的例子可参见第二十六章原子类和并发容器。ProducerConsumerPatternExample类包含了一个共享的整形队列sharedQueue,MAX_LEN指定了该队列能容纳的最多元素个数。方法produce()不停的向队列添加整数10,直到队列被填满。方法consume()不停的从队列中取出整数,直到队列被取空。
值得注意的是,produce()和consume()方法都将wait()和notifyAll()方法的调用放在synchronized语句块中,这是因为在调用wait()和notifyAll()方法时,该线程需要事先获得sharedQueue的monitor的拥有权(ownership)。其次,它们将wait()方法调用放在while循环中是为了防备“假唤醒”发生。如果“假唤醒”发生了,线程需要再次检查唤醒条件是否满足。因为produce()和consume()的绝大部分代码都在synchronized代码块中,在同一时刻,只有一个线程才能运行synchronized代码块中的代码。
import java.util.Queue;
import java.util.LinkedList;
public class ProducerConsumerPatternExample {
private Queue<Integer> sharedQueue = new LinkedList<Integer>();
private final int MAX_LEN = 3;
public void produce() {
while(true) {
// 获取sharedQueue的monitor
synchronized (sharedQueue) {
// 防止“假唤醒”发生,在wait()方法返回时,还需要再检查一些唤醒条件是否满足
while (sharedQueue.size() == MAX_LEN) {
try {
System.out.println("Queue is full. Producer Waits.");
// 队列已满,等待Consumer移除元素,并唤醒自己
sharedQueue.wait();
} catch (Exception ex) {}
}
// 填充元素
sharedQueue.add(10);
System.out.println("Producing integer 10");
// 通知 Consumer
sharedQueue.notifyAll();
}
}
}
public void consume() {
for (;;) {
// 获取sharedQueue的monitor
synchronized (sharedQueue) {
// 防止“假唤醒”发生,在wait()方法返回时,还需要再检查一些唤醒条件是否满足
while (sharedQueue.isEmpty()) {
try {
System.out.println("Queue is empty. Consumer Waits.");
// 队列已空,等待Producer填充元素,并唤醒自己
sharedQueue.wait();
} catch (Exception ex) {}
}
// 移除元素
Integer i = sharedQueue.remove();
System.out.println("Consuming " + i);
// 通知 Producer
sharedQueue.notifyAll();
}
}
}
public static void main(String[] args) {
// 在同一个ProducerConsumerPatternExample对象上运行两个线程
// 这两个线程分别运行produce()方法和consume()方法
ProducerConsumerPatternExample example = new ProducerConsumerPatternExample();
Runnable producer = () -> {example.produce();};
Runnable consumer = () -> {example.consume();};
Thread producerThread = new Thread(producer);
Thread consumerThread = new Thread(consumer);
producerThread.start();
consumerThread.start();
producerThread.join();
consumerThread.join();
}
}
程序运行结果如下:
> java ProducerConsumerPatternExample
Producing integer 10
Producing integer 10
Producing integer 10
Queue is full. Producer Waits.
Consuming 10
Consuming 10
Consuming 10
Queue is empty. Consumer Waits.
Producing integer 10
Producing integer 10
Producing integer 10
Queue is full. Producer Waits.
Consuming 10
Consuming 10
Consuming 10
...
本小节讲解了Wait/Notify机制。虽然小水滴并不推荐直接使用Wait/Notify机制,但是学习这个机制有助于加深理解多线程编程的复杂性,以帮助学习后续介绍的其他线程同步机制。
注册用户登陆后可留言