基本概念
在Java的 Object 类中有三个 fina l的方法允许线程之间进行资源对象锁的通信。分别是:wait 、notify、notifyAll。
调用这些方法的当前线程必须拥有此对象的监视器(即锁),否则将会报 java.lang.IllegalMonitorStateException exception 异常。
原理分析
首先来看 Object 的结构:
1.wait
Object的 wait 方法有三个重载方法:
wait() 是无限期(一直)等待,直到其它线程调用 notify 或 notifyAll 方法唤醒当前的线程;
wait(long timeout) 和wait(long timeout, int nanos) 允许传入 当前线程在被唤醒之前需要等待的时间,timeout为毫秒数,nanos为纳秒数。
2.notify
notify 方法只唤醒一个等待(对象的)线程并使该线程开始执行。
所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
3.notifyAll
notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。
实例探究
下面来讲讲 java 多线程的典型例子 - 生产者与消费者。它们就是依靠 wait、notify/notifyAll 来实现的。
在下面的代码中,定义了三个类:Depot (仓库),Producer (生产者),Customer (消费者)。
- Depot ,定义了生产/消费两个动作,同一时间只能执行一个动作。并且规定当仓库未满仓时,可以生产物品,满仓后停止该动作;当仓库存在物品时,可以消费物品,空仓后停止该动作。
public class Depot {
// 仓库容量
private int capacity;
// 仓库实际物品数量
private int size;
public Depot(int capacity) {
this.capacity = capacity;
this.size = 0;
}
// 制造商品
public synchronized void produce(int val) throws InterruptedException {
System.out.println("制造商品前,当前仓库里有物品:" + size);
// 仓库未满时
while (size < capacity) {
// 实际生产的物品数量(不能超出容量)
int num = (size + val) > capacity ? capacity - size : val;
//添加进仓库
size += num;
System.out.println("实际生产了物品:" + num + ",当前仓库里有物品:" + size);
notify();
}
wait();
}
// 消费商品
public synchronized void consume(int val) throws InterruptedException {
// 仓库存在物品时可以消费
while (size > 0) {
int num = size > val ? val : size;
size -= num;
System.out.println("实际消费了物品:" + num + ",当前仓库里有物品:" + size);
notify();
}
wait();
}
}
- Producer ,调用 Depot 的生产动作,每次生产 7 件物品
class Producer implements Runnable {
private Depot depot;
private int val;
public Producer(Depot depot,int val) {
this.depot = depot;
this.val = val;
}
public void run() {
try {
depot.produce(val);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- Customer ,调用 Depot 的消费动作,每次生产 6 件物品
public class Customer implements Runnable {
private Depot depot;
private int val;
public Customer(Depot depot,int val) {
this.depot = depot;
this.val = val;
}
public void run() {
try {
depot.consume(val);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 调用过程
Depot depot = new Depot(100);
Producer pro = new Producer(depot,7);
Customer cus = new Customer(depot, 6);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(cus);
t1.start();
t2.start();
- 输出结果
//开始生产,注意最后一次生产
制造商品前,当前仓库里有物品:0
实际生产了物品:7,当前仓库里有物品:7
实际生产了物品:7,当前仓库里有物品:14
实际生产了物品:7,当前仓库里有物品:21
实际生产了物品:7,当前仓库里有物品:28
实际生产了物品:7,当前仓库里有物品:35
实际生产了物品:7,当前仓库里有物品:42
实际生产了物品:7,当前仓库里有物品:49
实际生产了物品:7,当前仓库里有物品:56
实际生产了物品:7,当前仓库里有物品:63
实际生产了物品:7,当前仓库里有物品:70
实际生产了物品:7,当前仓库里有物品:77
实际生产了物品:7,当前仓库里有物品:84
实际生产了物品:7,当前仓库里有物品:91
实际生产了物品:7,当前仓库里有物品:98
实际生产了物品:2,当前仓库里有物品:100
//停止生产,开始消费。注意最后一次消费
实际消费了物品:6,当前仓库里有物品:94
实际消费了物品:6,当前仓库里有物品:88
实际消费了物品:6,当前仓库里有物品:82
实际消费了物品:6,当前仓库里有物品:76
实际消费了物品:6,当前仓库里有物品:70
实际消费了物品:6,当前仓库里有物品:64
实际消费了物品:6,当前仓库里有物品:58
实际消费了物品:6,当前仓库里有物品:52
实际消费了物品:6,当前仓库里有物品:46
实际消费了物品:6,当前仓库里有物品:40
实际消费了物品:6,当前仓库里有物品:34
实际消费了物品:6,当前仓库里有物品:28
实际消费了物品:6,当前仓库里有物品:22
实际消费了物品:6,当前仓库里有物品:16
实际消费了物品:6,当前仓库里有物品:10
实际消费了物品:6,当前仓库里有物品:4
实际消费了物品:4,当前仓库里有物品:0