java suprious wakeup_Java生产消费问题与虚假唤醒(spurious wakeup)

参考并发容器-阻塞队列 第四部分“阻塞队列的实现原理”。

参考代码生产者-消费者

1.缺少wait会出现的问题

三个类:售货员Clerk,工厂Factory,消费者Consumer

Factory和Consumer共享Clerk对象

class Clerk{

//商品数量默认是0,volatile关键字保证内存可见性

private volatile int product=0;

//进货,synchronized关键字保证原子性,互斥性

public synchronized void get(){

if(product>10){

System.out.println("货满了");

}else {

++product;

System.out.println(Thread.currentThread().getName()+"进货"+product);

}

}

//售货

public synchronized void sale(){

if(product<=0){

System.out.println("没货了");

}else{

System.out.println(Thread.currentThread().getName()+"卖货"+product);

--product;

}

}

}

class Factory implements Runnable{

private Clerk clerk;

Factory(Clerk clerk){

this.clerk = clerk;

}

@Override

public void run() {

for(int i=0;i<20;i++){

clerk.get();//进货

}

}

}

class Consumer implements Runnable{

private Clerk clerk;

Consumer(Clerk clerk){

this.clerk = clerk;

}

@Override

public void run() {

for(int i=0;i<20;i++){

clerk.sale();//卖货

}

}

}

public static void main(String[] args) {

Clerk clerk = new Clerk();

Factory factory = new Factory(clerk);

Consumer consumer = new Consumer(clerk);

Thread tf = new Thread(factory);

Thread tc = new Thread(consumer);

tf.start();

tc.start();

}

输出结果:

Thread-0进货1

Thread-0进货2

Thread-0进货3

Thread-0进货4

Thread-0进货5

Thread-0进货6

Thread-0进货7

Thread-0进货8

Thread-0进货9

Thread-0进货10

Thread-0进货11

货满了

货满了

货满了

货满了

货满了

货满了

货满了

货满了

货满了

Thread-1卖货11

Thread-1卖货10

Thread-1卖货9

Thread-1卖货8

Thread-1卖货7

Thread-1卖货6

Thread-1卖货5

Thread-1卖货4

Thread-1卖货3

Thread-1卖货2

Thread-1卖货1

没货了

没货了

没货了

没货了

没货了

没货了

没货了

没货了

没货了

问题:上述的情况是当没货的时候还会继续调用该方法,从而占用资源,二货满的情况下也会重复调用进货方法,占用资源,这样是不合理的。

解决方式:当货满了,应该停止进货,释放锁让消费者消费,当没货了应该停止消费释放锁,让进货,这是我们想要的逻辑。

使用wait()和notifyAll()这两个方法来实现。

class Clerk{

//商品数量默认是0

private volatile int product=0;

//进货

public synchronized void get(){

if(product>10){

System.out.println("货满了");

try {

this.wait();//等待并释放clerk的对象锁,进入线程队列等待被唤醒

} catch (InterruptedException e) {

e.printStackTrace();

}

}else {

++product;

System.out.println(Thread.currentThread().getName()+"进货"+product);

notifyAll();//唤醒等待的线程

}

}

//售货

public synchronized void sale(){

if(product<=0){

System.out.println("没货了");

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}else{

System.out.println(Thread.currentThread().getName()+"卖货"+product);

--product;

notifyAll();

}

}

}

结果

Thread-0进货1

Thread-0进货2

Thread-0进货3

Thread-0进货4

Thread-0进货5

Thread-0进货6

Thread-0进货7

Thread-0进货8

Thread-0进货9

Thread-0进货10

Thread-0进货11

货满了

Thread-1卖货11

Thread-1卖货10

Thread-1卖货9

Thread-1卖货8

Thread-1卖货7

Thread-1卖货6

Thread-1卖货5

Thread-1卖货4

Thread-1卖货3

Thread-1卖货2

Thread-1卖货1

没货了

Thread-0进货1

Thread-0进货2

Thread-0进货3

Thread-0进货4

Thread-0进货5

Thread-0进货6

Thread-0进货7

Thread-0进货8

Thread-1卖货8

Thread-1卖货7

Thread-1卖货6

Thread-1卖货5

Thread-1卖货4

Thread-1卖货3

Thread-1卖货2

Thread-1卖货1

2.线程阻塞无法唤醒

当产品为1时,生成者线程生产结束;此时Consumer还处于wait无法得到唤醒。

这种情景对吗?这种问题什么情况下会发生?

解决方式:去掉else,每次都会唤醒另外一方的线程。

class Clerk{

//商品数量默认是0

private volatile int product=0;

//进货

public synchronized void get(){

if(product>10){

System.out.println("货满了");

try {

this.wait();//等待并释放clerk的对象锁,进入线程队列等待被唤醒

} catch (InterruptedException e) {

e.printStackTrace();

}

}

++product;

System.out.println(Thread.currentThread().getName()+"进货"+product);

notifyAll();//唤醒等待的线程

}

//售货

public synchronized void sale(){

if(product<=0){

System.out.println("没货了");

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(Thread.currentThread().getName()+"卖货"+product);

--product;

notifyAll();

}

}

3.虚假唤醒

Clerk clerk = new Clerk();

Factory factory = new Factory(clerk);

Consumer consumer = new Consumer(clerk);

Thread tf = new Thread(factory);

Thread tc = new Thread(consumer);

Thread tc2 = new Thread(consumer);

tf.start();

tc.start();

tc2.start();

会出现负数!

没货了

没货了

Thread-0进货1

Thread-2卖货1

没货了

Thread-1卖货0

没货了

Thread-2卖货-1

没货了

Thread-1卖货-2

没货了

Thread-2卖货-3

没货了

Thread-1卖货-4

没货了

Thread-2卖货-5

没货了

Thread-1卖货-6

原因

两个消费者都处于wait状态,然后生产者生产了一个notifyAll,两个消费者同时往下执行,导致product为负数。

解决方法:防止虚假唤醒,应该放在循环中,多次进行检查,直到满足条件才进行下一步

4.守护线程解决线程阻塞

当多个消费者和一个生产者的时候,生产者有可能先结束循环,但是消费者还没结束,结果到了其他消费者的时候发现product是小于0的于是就wait,程序一直等待得不到结束,就会一直在wait()

解决方式

在共享资源clerk类中定义生产者线程标志位,在main线程中创建一个线程设置为守护线程 并启动,在该守护线程中创建匿名内部类Runnable,并在run方法中判断生产者线程isAlive() 。如果生产者线程结束,就把标志位置为false,该标识位和消费者线程的while判断条件中串联,当生产者线程为false的之后短路,使得消费和线程啥都不做,直到线程结束。

Clerk中设置Factory 线程标志位

private boolean facctoryFlg = true;//工厂线程结束的标志位,为false表示线程执行完毕

public boolean isFacctoryFlg() {

return facctoryFlg;

}

public void setFacctoryFlg(boolean facctoryFlg) {

this.facctoryFlg = facctoryFlg;

}

Main中创建守护线程

//创建守护线程

Thread daemon = new Thread(new Runnable() {

@Override

public void run() {

while(true){

if(!tf.isAlive()){

clerk.setFacctoryFlg(false);

System.out.println("factory--------------"+tf.isAlive());

break;

}

}

}

});

daemon.setDaemon(true);//设置为守护线程(后台线程)

daemon.start();

修改·Clerk的sale方法

public synchronized void sale(){

while(product<=0){

//当Factory线程结束的时候,直接结束sale方法

if(!isFacctoryFlg()){

return;

}

System.out.println("没货了");

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(Thread.currentThread().getName()+"卖货"+product);

--product;

notifyAll();

}

参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值