wait与nofity关键字是Java多线程的关键,主要是用来线程之间的通信用的,如果你的Java程序中有两个线程——即生产者和消费者,那么生产者可以通知消费者,让消费者开始消耗数据,因为队列缓 冲区中有内容待消费(不为空)。相应的,消费者可以通知生产者可以开始生成更多的数据,因为当它消耗掉某些数据后缓冲区不再为满。
我们可以利用wait()来让一个线程在某些条件下暂停运行。例如,在生产者消费者模型中,生产者线程在缓冲区为满的时候,消费者在缓冲区为空的时 候,都应该暂停运行。如果某些线程在等待某些条件触发,那当那些条件为真时,你可以用 notify 和 notifyAll 来通知那些等待中的线程重新开始运行。不同之处在于,notify 仅仅通知一个线程,并且我们不知道哪个线程会收到通知,然而 notifyAll 会通知所有等待中的线程。换言之,如果只有一个线程在等待一个信号灯,notify和notifyAll都会通知到这个线程。但如果多个线程在等待这个信 号灯,那么notify只会通知到其中一个,而其它线程并不会收到任何通知,而notifyAll会唤醒所有等待中的线程。
1. 你可以使用wait和notify函数来实现线程间通信。你可以用它们来实现多线程(>3)之间的通信。
永远在synchronized的函数或对象里使用wait、notify和notifyAll,不然Java虚拟机会生成 IllegalMonitorStateException。
永远在while循环里而不是if语句下使用wait。这样,循环会在线程睡眠前后都检查wait的条件,并在条件实际上并未改变的情况下处理唤醒通知。
永远在多线程间共享的对象(在生产者消费者模型里即缓冲区队列)上使用wait。
基于前文提及的理由,更倾向用 notifyAll(),而不是 notify()。
以下我给出一个简单的消费者生产的例子帮助理解wait与notify关键字:
class WareHouse {
// 存放货物的仓库,用栈结构模拟
Stack<Object> goods = new Stack<Object>();
// 仓库最大的保存量
public static int maxSize = 100;
// 初始化容量
WareHouse(int initSize) {
if (initSize < 0)
return;
for (int i = 0; i < initSize; i++) {
goods.add(new Object());
}
}
// 获取仓库当前容量
public int size() {
return goods.size();
}
// 向仓库中添加货物
public boolean addGood() {
return goods.add(new Object());
}
// 移除仓库当中的货物
public boolean removeGood() {
if (goods.size() == 0)
return false;
goods.pop();
return true;
}
}
/**
*
* @author micro
* @description 消费者
*
*/
class Consumer implements Runnable {
// 共享变量wareHouse
private WareHouse wareHouse;
private int num;
Consumer(WareHouse wareHouse) {
this.wareHouse = wareHouse;
}
// 消费数量
public void consum(int num) {
this.num = num;
}
public void run() {
// 对仓库进行加锁
synchronized (wareHouse) {
try {
System.out.println("我是消费者");
wareHouse.notify(); // 唤醒阻塞在wareHouse对象上的线程
System.out.println("仓库当前保存量:" + wareHouse.size() + ",最大保存量为:" + wareHouse.maxSize);
if ((wareHouse.size() - num) > 0) {
for (int i = 0;i < num;i ++) {
wareHouse.removeGood();
}
System.out.println("消费了 :" + num + ",库存还有:" + wareHouse.size());
} else {
System.out.println("需要消费 :" + num + ",当前库存不足");
}
System.out.println("消费完毕");
System.out.println("当前库存:" + wareHouse.size());
wareHouse.wait(); // 当前线程放弃wareHouse对象上的锁,并进入阻塞状态
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
/**
*
* @author micro
* @description 生产者
*
*/
class Producer implements Runnable {
private WareHouse wareHouse;
private int num;
Producer(WareHouse wareHouse) {
this.wareHouse = wareHouse;
}
// 生产的数量
public void produce(int num) {
this.num = num;
}
public void run() {
// 对仓库进行加锁
synchronized (wareHouse) {
try {
System.out.println("我是生产者");
wareHouse.notify(); // 唤醒阻塞在wareHouse上的线程
// 仓库生产后还小于最大量
System.out.println("仓库当前保存量:" + wareHouse.size() + ",最大保存量为:" + wareHouse.maxSize);
if (wareHouse.size() + num < wareHouse.maxSize) {
System.out.println("仓库可以生产: " + (wareHouse.maxSize - wareHouse.size() )+ ",仓库需要生产:" + num);
for (int i = 0; i < num; i++) {
wareHouse.addGood();
}
System.out.println("生产:" + num + ",仓库生产完毕");
} else {
System.out.println("无法生产");
}
System.out.println("当前库存:" + wareHouse.size());
wareHouse.wait(); // 当前线程放弃wareHouse对象上的锁,并进入阻塞状态
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
演示:
// 共享变量 (加锁对象)
WareHouse wareHouse = new WareHouse(100);
// 消费者
Consumer consumer1 = new Consumer(wareHouse);
consumer1.consum(60);
// 生产者
Producer producer1 = new Producer(wareHouse);
producer1.produce(40);
Producer producer2 = new Producer(wareHouse);
producer2.produce(10);
// 初始化三个线程
Thread t1 = new Thread(consumer1);
Thread t2 = new Thread(producer1);
Thread t3 = new Thread(producer2);
// 启动线程
t1.start();
t2.start();
t3.start();
// 主线程等待生产消费结束
try {
Thread.currentThread().sleep(1000);
} catch (Exception ex) {
ex.printStackTrace();
}
// 此时最后一个消费者或者是生产者肯定是在wait状态阻塞
synchronized (wareHouse) { // 如果没有此代码片段,主线程永远不会结束,因为最后t3线程肯定会被阻塞不会被唤醒
// 唤醒阻塞在wareHouse对象上阻塞的线程
wareHouse.notify();
}
// 主线程结束
System.out.println("结束");
打印结果:
我是消费者
仓库当前保存量:100,最大保存量为:100
消费了 :60,库存还有:40
消费完毕
当前库存:40
我是生产者
仓库当前保存量:40,最大保存量为:100
仓库可以生产: 60,仓库需要生产:10
生产:10,仓库生产完毕
当前库存:50
我是生产者
仓库当前保存量:50,最大保存量为:100
仓库可以生产: 50,仓库需要生产:40
生产:40,仓库生产完毕
当前库存:90
结束
下面演示一个关于银行取钱的笔试题
/**
* 假设某个银行,它可以接受顾客的汇款,每做一次汇款,便可以计算出汇款的总额。现在有两个顾客对同一个银行账号操作,一个顾客分3次,每次将100元钱存入,
* 每次存入后将钱的总额输出;另外一个顾客也分3次,每次取出100元钱,每次取出后将剩余的钱的总额输出。编写一个程序模拟实际的操作过程。
* @author micro_hz
*
*/
public class BankThreadTest {
public static void main(String[] args) {
final Bank bank = new Bank();
new Thread(new Runnable (){
@Override
public void run() {
for (int i = 0;i < 3;i ++) {
bank.addMoney(100);
}
}
},"顾客A").start();
new Thread(new Runnable (){
@Override
public void run() {
for (int i = 0;i < 3;i ++) {
bank.carrayMoney(100);
}
}
},"顾客B").start();
}
}
class Bank implements Runnable {
private Integer account = 0;
@Override
public void run() {
}
public synchronized void addMoney(Integer money) {
account += money;
System.out.println(Thread.currentThread().getName() + "存入 : " + money + ",剩余 : " + account);
notify(); // 可能有人等着取钱,唤醒该线程
}
public synchronized void carrayMoney(Integer money) {
if (account - money < 0) {
// 如果钱小于0,只能存钱
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
account -= money;
System.out.println(Thread.currentThread().getName() + "取出 :" + money + ",剩余 : " + account);
}
}
用阻塞队列进行模拟生产消费者:
public static void main(String[] args) {
WareHouse wareHouse = new WareHouse();
Producer p1 = new Producer(wareHouse);
p1.size = 100;
new Thread(p1).start();
Consumer c1 = new Consumer(wareHouse);
c1.comsume(100);
new Thread(c1).start();
}
static class WareHouse {
// 用阻塞队列完成生产消费
private LinkedBlockingQueue<Object> linkedBlockingQueue = new LinkedBlockingQueue<Object>();
public void consume() {
try {
System.out.println("消费了---" + linkedBlockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void add(Object o) {
System.out.println("生产了---" + linkedBlockingQueue.add(o));
}
}
static abstract class AbstractOwner {
protected WareHouse wareHouse;
public AbstractOwner(WareHouse wareHouse) {
this.wareHouse = wareHouse;
}
}
static class Producer extends AbstractOwner implements Runnable {
private int size;
public Producer(WareHouse wareHouse) {
super(wareHouse);
}
public void produce(int size) {
this.size = size;
}
@Override
public void run() {
for (int i = 0;i < size;i ++) {
// 这里让消费者更多占用cpu
Thread.currentThread().yield();
this.wareHouse.add(new Object());
}
}
}
static class Consumer extends AbstractOwner implements Runnable {
private int size;
public Consumer(WareHouse wareHouse) {
super(wareHouse);
}
public void comsume(int size) {
this.size = size;
}
@Override
public void run() {
for (int i = 0;i < size;i ++) {
this.wareHouse.consume();
}
}
}