java中wait的场景,Java-wait、notify、notifyAll

关键字wait、notify、notifyAll

大家都知道wait、notify、notifyAll这三个是Object提供的线程间协作的方法,常用在生产消费者模式,那么wait跟sleep有什么区别呢?wait、notify、notifyAll又该如何使用呢。

wait跟sleep区别

(1)共同点,wait、sleep都会让当前线程进入阻塞等待状态,并释放CPU时间片,在满足某个条件后被唤醒,例如timeout、中的;

(2)不同点,wait是Objec的方法,sleep是Thread的方法;

(3)异同点,wait会释放锁,而sleep不会释放;

wait、notify、notifyAll使用

场景:生产消费者模式

mProducerThread是生产者线程,负责生产商品,如果不能继续生产则wait;mConsumerThread是消费者线程,负责消费商品,如果不能继续消费则wait。

使用规则:

(1)wait、notify、notifyAll必须在同步代码中执行;

(2)wait必须在while循环内执行。

public class Goods {

//生产者线程

private Thread mProducerThread = new Thread() {

@Override

public void run() {

produce();

}

};

//消费者线程

private Thread mConsumerThread = new Thread() {

@Override

public void run() {

consume();

}

};

public void produce() {

while (true) {

synchronized (Goods.this) {

while (是否不能生产) {//不满足生产条件

try {

Goods.this.wait();//释放锁,让出CPU

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//生产商品

Goods.this.notifyAll();

}

}

}

public void consume() {

while (true) {

synchronized (Goods.this) {

while (是否不能消费) {//不满足消费条件

try {

Goods.this.wait();//释放锁,让出CPU

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//消费商品

Goods.this.notifyAll();

}

}

}

//开启任务

public void start() {

mProducerThread.start();

mConsumerThread.start();

}

}

以下是消费者的流程图,生产者也类似,

06a726241d21

消费者.png

使用规则思考

wait、notify、notifyAll,为什么必须在同步代码中执行

(1)以上述代码为例,当不能继续生产或不能继续消费时,需要其他线程消费或者生成商品,则调用wait释放锁让出CPU,wait是释放锁,只有获得锁才能释放锁,没有获得锁哪来释放锁呢,另外,线程间协作通常会共享变量,对共享变量的操作放在同步代码块中,可以解决线程安全问题,所以wait必须在同步代码执行,否则会抛出java.lang.IllegalMonitorStateException: object not locked by thread before wait()异常;

(2)notify、notifyAll同理,生产完商品或消费完商品,需要唤醒其他线程并且释放锁,那么其他线程才有机会获得锁,所以在退出同步代码时调用notify、notifyAll,如果不在同步代码中执行,则抛出java.lang.IllegalMonitorStateException: object not locked by thread before notify();

(3)总结一点,没有获取锁,那就谈不上wait、notify、notifyAll;

notify和notifyAll区别

(1)notify,唤醒等待同一个对象的某个线程去竞争锁,至于是哪个线程获取锁,由CPU调度;

(2)notifyAll,唤醒等待同一个对象的所有线程去竞争锁,至于是哪个线程获取锁,由CPU调度,听起来跟notify一样;

(3)上述代码,将Goods.this.notifyAll()换成Goods.this.notify效果一样。假如,mConsumerThread消费者先获得锁进入同步代码,但是发现不能继续消费商品,则wait释放锁让出CPU处于等待状态,那么mProducerThread生产者线程竞争获得锁进入同步代码而且可以继续生产商品,则开始生产商品,生产完以后在退出同步代码之前调用notifyAll,那么mConsumerThread 就会被唤醒去竞争锁。试想着,在等待的只有mConsumerThread,所以无论是用notifyAll唤醒多个,还是notify唤醒某一个,效果都一样。如果是只有一个mConsumerThread和一个mProducerThread的情况,用notifyAll或notify都一样,因为在等待的只有一个。

如果是多个ConsumerThread或者多个

ProducerThread的情况,用notifyAll显然更适合。假如,mProducerThread生产完商品,希望唤醒消费者来消费,那么所有消费者都应该得到唤醒,如果使用notify,极端情况,只有其中某个消费者一直被唤醒消费,属于过得饱和,而其他消费者 过度饥饿,显示不是使用多线程的初衷;还有一种极端情况,还是只有其中某个消费者一直被唤醒,但是因为某个条件不满足,无法消费商品,而生产的商品已经满了,一直处于wait,而消费者一直不消费商品,任务无法继续执行下去,假死现象。

(4)永远是单个线程处于等待,用notify或notifyAll效果都一样,如果是多线程处于等待,显然用notifyAll更合适。

wait为什么必须在while循环内执行

上述代码的consume方法,将while改成if,当mProducerThread生产者生产完商品唤醒消费者,mConsumerThread获得锁不再判断消费条件是否成立,直接去消费商品,这样没有如何问题,因为很明确知道mConsumerThread被唤醒且获得锁,肯定是有商品可以消费,所以无需再判断条件是否成立。

但如果是以下场景则不同,

mConsumerThread1 与mProducerThread1,以及mConsumerThread2 与mProducerThread2是成对关系。

mConsumerThread1 只能消费mProducerThread1生产的商品,mConsumerThread2 只能消费mProducerThread2生产的商品。

当mConsumerThread1 、mConsumerThread2都不满足消费条件处于等待状态,这时mProducerThread1 生产了商品,调用notifyAll唤醒线程来消费,因为调用的是 notifyAll,所以mConsumerThread1、mConsumerThread2都会被唤醒,如果正好是mConsumerThread2获得了锁,需要重新判断条件是否满足,因为mConsumerThread2是跟mProducerThread2对应的,这时候while循环就起用了。

如果是有多组执行条件,唤醒存在假唤醒的情况(中断唤醒),那么用while循环显然更合适,这也是Java推荐的。

public class Goods {

//生成者线程1

private Thread mProducerThread1 = new Thread() {

@Override

public void run() {

produce1();

}

};

//消费者线程2

private Thread mConsumerThread1 = new Thread() {

@Override

public void run() {

consume1();

}

};

//生成者线程2

private Thread mProducerThread2 = new Thread() {

@Override

public void run() {

produce2();

}

};

//消费者线程2

private Thread mConsumerThread2 = new Thread() {

@Override

public void run() {

consume2();

}

};

public void produce1() {

while (true) {

synchronized (Goods.this) {

while (是否不能生产) {//不满足生成条件1

try {

Goods.this.wait();//释放锁,让出CPU

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//生成商品

Goods.this.notifyAll();

}

}

}

public void consume1() {

while (true) {

synchronized (Goods.this) {

while (是否不能消费) {//不满足消费条件1

try {

Goods.this.wait();//释放锁,让出CPU

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//消费商品

Goods.this.notifyAll();

}

}

}

public void produce2() {

while (true) {

synchronized (Goods.this) {

while (是否不能生产) {//不满足生产条件2

try {

Goods.this.wait();//释放锁,让出CPU

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//生成商品

Goods.this.notifyAll();

}

}

}

public void consume2() {

while (true) {

synchronized (Goods.this) {

while (是否不能消费商品) {//不满足消费条件2

try {

Goods.this.wait();//释放锁,让出CPU

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//消费商品

Goods.this.notifyAll();

}

}

}

//开启任务

public void start() {

mProducerThread1.start();

mConsumerThread1.start();

mProducerThread2.start();

mConsumerThread2.start();

}

}

总结:

(1)wait、notify、notifyAll必须在同步代码中执行;

(2)wait必须在while循环内执行;

(3)notify比notifyAll更容易出现死锁。

以上分析有不对的地方,请指出,互相学习,谢谢哦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值