浅析多线程的虚假唤醒

       首先,关于if和while的使用,在单线程环境下是比较容易区分的,在此,不赘述。今天,主要聊聊在多线程环境下,if和while检测条件变量的区别。之前这个问题也一直困扰着我,明明是条件判断,第一反应就是if,可是为什么要用while呢?下面结合生产者消费者模型来简单分析下。

     1.先使用if。我们先用只有一个生产者和一个消费者来看看结果如何。代码如下:

//生产者消费者问题

public class ProducerConsumer {
	private int[] source=new int[5];  //共享资源
	private int count=0;   //统计个数
	private int index=0;    //数组下标
	
	//生产商品
	public synchronized void produce() {
		//如果大于容器的容量,就停止生产,等待消费者消费
		if(index>=source.length) {
			try {
				System.out.println(Thread.currentThread().getName()+"准备等待.........");
				wait();
				System.out.println(Thread.currentThread().getName()+"等待结束");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"生产第"+count+"个数据");
		source[index++]=count++;
		notifyAll();
	}
	
	//消费商品
	public synchronized void consume() {
		//如果没有商品可以消费,就等待生产者生产
		if(index<=0) {
			try {
				System.out.println(Thread.currentThread().getName()+"准备等待.........");
				wait();
				System.out.println(Thread.currentThread().getName()+"等待结束");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"消费第"+source[--index]+"个数据");
		notifyAll();
	}
	
	//生产者
	class Producer implements Runnable{
	
		@Override
		public void run() {
			for(int i=0;i<10;i++){
				produce();
			}			
		}
		
	}
	//消费者
	class Consumer implements Runnable{
		
		@Override
		public void run() {
			for(int i=0;i<10;i++){
				consume();
			}			
		}
		
	}
	
	public static void main(String[] args) {
		ProducerConsumer pc=new ProducerConsumer();
		Producer p=pc.new Producer();
		Consumer c=pc.new Consumer();
		Thread t1=new Thread(p);
		t1.setName("生产者0");
		Thread t2=new Thread(c);
		t2.setName("消费者0");
		t1.start();
		t2.start();
	}

}

运行的结果一切正常:

生产者0生产第0个数据
生产者0生产第1个数据
生产者0生产第2个数据
生产者0生产第3个数据
生产者0生产第4个数据
生产者0准备等待.........
消费者0消费第4个数据
消费者0消费第3个数据
消费者0消费第2个数据
消费者0消费第1个数据
消费者0消费第0个数据
消费者0准备等待.........
生产者0等待结束
生产者0生产第5个数据
生产者0生产第6个数据
生产者0生产第7个数据
生产者0生产第8个数据
生产者0生产第9个数据
消费者0等待结束
消费者0消费第9个数据
消费者0消费第8个数据
消费者0消费第7个数据
消费者0消费第6个数据
消费者0消费第5个数据
 

2.现在考虑一个生产者,两个消费者的情况,只需要增加一个消费者线程即可,其他不用改动,将main方法改为:

	public static void main(String[] args) {
		ProducerConsumer pc=new ProducerConsumer();
		Producer p=pc.new Producer();
		Consumer c=pc.new Consumer();
		Thread t1=new Thread(p);
		t1.setName("生产者0");
		Thread t2=new Thread(c);
		t2.setName("消费者0");
		Thread t3=new Thread(c);
		t3.setName("消费者1");
		t1.start();
		t2.start();
		t3.start();
	}

意外出现了。居然出错了,数组越界,如下:

生产者0生产第0个数据
生产者0生产第1个数据
生产者0生产第2个数据
生产者0生产第3个数据
生产者0生产第4个数据
生产者0准备等待.........
消费者0消费第4个数据
消费者0消费第3个数据
消费者0消费第2个数据
消费者0消费第1个数据
消费者0消费第0个数据
消费者0准备等待.........
消费者1准备等待.........
生产者0等待结束
生产者0生产第5个数据
生产者0生产第6个数据
生产者0生产第7个数据
生产者0生产第8个数据
生产者0生产第9个数据
消费者0等待结束
消费者0消费第9个数据
消费者0消费第8个数据
消费者0消费第7个数据
消费者0消费第6个数据
消费者0消费第5个数据
消费者1等待结束
Exception in thread "消费者1" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 5
    at test.ProducerConsumer.consume(ProducerConsumer.java:41)
    at test.ProducerConsumer$Consumer.run(ProducerConsumer.java:62)
    at java.base/java.lang.Thread.run(Thread.java:834)

可以看到,当生产者生产完成时,通知了两个消费者线程,但是当消费者0消费完后,数组的下标为0,但此时消费者1从wait()方法的后面继续执行,不再判断条件是否满足,所以出现了数组越界的情况。

3.把条件检测if改为while就可以解决上述问题。输出信息如下:

生产者0生产第0个数据
生产者0生产第1个数据
生产者0生产第2个数据
生产者0生产第3个数据
生产者0生产第4个数据
生产者0准备等待.........
消费者0消费第4个数据
消费者0消费第3个数据
消费者0消费第2个数据
消费者0消费第1个数据
消费者0消费第0个数据
消费者0准备等待.........
消费者1准备等待.........
生产者0等待结束
生产者0生产第5个数据
生产者0生产第6个数据
生产者0生产第7个数据
生产者0生产第8个数据
生产者0生产第9个数据
消费者0等待结束
消费者0消费第9个数据
消费者0消费第8个数据
消费者0消费第7个数据
消费者0消费第6个数据
消费者1等待结束
消费者1消费第5个数据
消费者1准备等待.........
消费者0准备等待.........

由此可以看到,这是由于虚假唤醒的缘故导致if判断在多线程环境下出错,因为它不再判断条件是否满足,继续从wait()方法之后执行。而while还会再次判断条件是否满足,如果不满足就不会执行。

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值