前提概要
在Java的Object类中有三个final的方法允许线程之间进行资源对象锁的通信,他们分别是: wait(), notify() and notifyAll()。
调用这些方法的当前线程必须拥有此对象监视器,否则将会报java.lang.IllegalMonitorStateException exception异常。
-
wait
Object的wait方法有三个重载方法,其中一个方法wait() 是无限期(一直)等待,直到其它线程调用notify或notifyAll方法唤醒当前的线程;另外两个方法wait(long timeout) 和wait(long timeout, int nanos)允许传入 当前线程在被唤醒之前需要等待的时间,timeout为毫秒数,nanos为纳秒数。 -
notify
notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。 -
notifyAll
notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。
这些方法可以使用于“生产者-消费者”问题,消费者是在队列中等待对象的线程,生产者是在队列中释放对象并通知其他线程的线程。
在线程唤醒机制中如果处理不当会出现虚假唤醒(spurious wakeup)的情况,也就是说线程在等待信号时即使条件不满足,但是线程仍然可能被唤醒,解决这种问题的办法是在条件判断时使用while而不使用if,例如:
while (num == 0) {
System.out.println("现有鸡蛋:" + num + "颗,消费者不可消费!");
this.wait();
}
让我们来看一个多线程作用于同一个对象的例子,我们使用wait, notify and notifyAll方法。
实现代码
package com.oumuv.producerandconsumer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 生产者与消费者问题:wait、notify(notifyAll)应用
* 一个篮子可存20颗鸡蛋,鸡蛋数量小于5时生产者开始生产鸡蛋,篮子满后生产者停止生产并进入等待状态,鸡蛋数量等于0时消费者不能消费
*
*/
public class ProducerAndConsumer2 {
public static void main(String[] args) {
ProducerAndConsumer2 pac = new ProducerAndConsumer2();
EggKep eggKep = pac.new EggKep();
Producer p1 = pac.new Producer(eggKep);
Consumer c1 = pac.new Consumer(eggKep);
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(p1);
executorService.submit(c1);
}
/**
* 生产者
*/
class Producer implements Runnable {
EggKep agg;
public Producer(EggKep agg) {
this.agg = agg;
}
@Override
public void run() {
try {
for (int i = 0; i < 50; i++) {
agg.produce();
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 消费者
*/
class Consumer implements Runnable {
EggKep agg;
public Consumer(EggKep agg) {
this.agg = agg;
}
@Override
public void run() {
try {
for (int i = 0; i < 50; i++) {
agg.consume();
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 鸡蛋篮子
*/
class EggKep {
int maxSize = 20;//篮子大小
int num = 0;//鸡蛋数量
boolean flag = true;//是否开始生产,默认true生产
public EggKep(int maxSize, int num) {
this.maxSize = maxSize;
this.num = num;
}
public EggKep() {
}
/**
*生产方法
*/
protected synchronized void produce() throws InterruptedException {
//为了避免虚假唤醒,需要不断的检测满足条件
while (!flag) {
this.wait();
}
System.out.println("生产了一颗鸡蛋,现有鸡蛋:" + (++num) + "颗");
if (num == maxSize) {
flag = false;
System.out.println("篮子满了,生产者停止工作!");
}
this.notify();
}
/**
*消费方法
*/
protected synchronized void consume() throws InterruptedException {
while (num == 0) {
System.out.println("现有鸡蛋:" + num + "颗,消费者不可消费!");
this.wait();
}
System.out.println("消费一颗鸡蛋,现有鸡蛋:" + (--num) + "颗");
if (num <= 5 && !flag) {
flag = true;
System.out.println("通知生产者开始工作!");
}
this.notify();
}
}
}
可能输出的结果
现有鸡蛋:0颗,消费者不可消费!
生产了一颗鸡蛋,现有鸡蛋:1颗
消费一颗鸡蛋,现有鸡蛋:0颗
生产了一颗鸡蛋,现有鸡蛋:1颗
生产了一颗鸡蛋,现有鸡蛋:2颗
生产了一颗鸡蛋,现有鸡蛋:3颗
消费一颗鸡蛋,现有鸡蛋:2颗
生产了一颗鸡蛋,现有鸡蛋:3颗
生产了一颗鸡蛋,现有鸡蛋:4颗
生产了一颗鸡蛋,现有鸡蛋:5颗
消费一颗鸡蛋,现有鸡蛋:4颗
生产了一颗鸡蛋,现有鸡蛋:5颗
生产了一颗鸡蛋,现有鸡蛋:6颗
生产了一颗鸡蛋,现有鸡蛋:7颗
消费一颗鸡蛋,现有鸡蛋:6颗
生产了一颗鸡蛋,现有鸡蛋:7颗
生产了一颗鸡蛋,现有鸡蛋:8颗
生产了一颗鸡蛋,现有鸡蛋:9颗
生产了一颗鸡蛋,现有鸡蛋:10颗
消费一颗鸡蛋,现有鸡蛋:9颗
生产了一颗鸡蛋,现有鸡蛋:10颗
生产了一颗鸡蛋,现有鸡蛋:11颗
生产了一颗鸡蛋,现有鸡蛋:12颗
消费一颗鸡蛋,现有鸡蛋:11颗
生产了一颗鸡蛋,现有鸡蛋:12颗
生产了一颗鸡蛋,现有鸡蛋:13颗
生产了一颗鸡蛋,现有鸡蛋:14颗
消费一颗鸡蛋,现有鸡蛋:13颗
生产了一颗鸡蛋,现有鸡蛋:14颗
生产了一颗鸡蛋,现有鸡蛋:15颗
生产了一颗鸡蛋,现有鸡蛋:16颗
生产了一颗鸡蛋,现有鸡蛋:17颗
消费一颗鸡蛋,现有鸡蛋:16颗
生产了一颗鸡蛋,现有鸡蛋:17颗
生产了一颗鸡蛋,现有鸡蛋:18颗
生产了一颗鸡蛋,现有鸡蛋:19颗
消费一颗鸡蛋,现有鸡蛋:18颗
生产了一颗鸡蛋,现有鸡蛋:19颗
生产了一颗鸡蛋,现有鸡蛋:20颗
篮子满了,生产者停止工作!
消费一颗鸡蛋,现有鸡蛋:19颗
消费一颗鸡蛋,现有鸡蛋:18颗
消费一颗鸡蛋,现有鸡蛋:17颗
消费一颗鸡蛋,现有鸡蛋:16颗
消费一颗鸡蛋,现有鸡蛋:15颗
消费一颗鸡蛋,现有鸡蛋:14颗
消费一颗鸡蛋,现有鸡蛋:13颗
消费一颗鸡蛋,现有鸡蛋:12颗
消费一颗鸡蛋,现有鸡蛋:11颗
消费一颗鸡蛋,现有鸡蛋:10颗
消费一颗鸡蛋,现有鸡蛋:9颗
消费一颗鸡蛋,现有鸡蛋:8颗
消费一颗鸡蛋,现有鸡蛋:7颗
消费一颗鸡蛋,现有鸡蛋:6颗
消费一颗鸡蛋,现有鸡蛋:5颗
通知生产者开始工作!
生产了一颗鸡蛋,现有鸡蛋:6颗
生产了一颗鸡蛋,现有鸡蛋:7颗
生产了一颗鸡蛋,现有鸡蛋:8颗
生产了一颗鸡蛋,现有鸡蛋:9颗
消费一颗鸡蛋,现有鸡蛋:8颗
生产了一颗鸡蛋,现有鸡蛋:9颗
生产了一颗鸡蛋,现有鸡蛋:10颗
生产了一颗鸡蛋,现有鸡蛋:11颗
消费一颗鸡蛋,现有鸡蛋:10颗
生产了一颗鸡蛋,现有鸡蛋:11颗
生产了一颗鸡蛋,现有鸡蛋:12颗
生产了一颗鸡蛋,现有鸡蛋:13颗
消费一颗鸡蛋,现有鸡蛋:12颗
.....