通过生产者消费者案例理解等待唤醒机制和虚假唤醒
2017-02-10 20:33
786 查看
首先引入下面这段生产者和消费者的程序,店员类作为生产产品和消费产品的中介,其中的数据product为共享数据,产品最多只能囤积5个,当产品达到5个还在生产时,就会提示“产品已满!”,类似地,如果产品只有0个了还在消费,会提示“缺货!”:
运行程序,结果如下:
这是一种不好的情况,因为当产品已满时,还在不停地生产,当缺货时,还在不停地消费。为此,我们引入等待唤醒机制:
再运行程序,就不会再出现上述的情况:
但是,现在,我们将产品的囤积上限设定为1(这种情况在现实中也是有可能出现的):
然后运行程序:
程序的输出貌似没有问题,但请注意图中箭头所指的地方,这表示程序没有结束,还一直在执行。这是因为,当循坏到最后一轮时,由于产品已满引发了wait()操作,然后生产者线程等待,随后消费者消费了一份产品,并唤醒等待的生产者线程,此时,被唤醒的生产者线程由于循环结束,直接结束了线程的执行,但是另一边,消费者线程没有结束,而且由于将产品消费完后再次进入了等待,但是生产者线程此时已经结束了,不能再唤醒消费者线程,所以便进入了死循环。
解决这种问题的方法时去掉Clerk类中get方法和sale方法的else,并将原来else中的代码直接提出,这样,就算线程结束,也会先再次唤醒等待的线程:
运行程序,不再死循环:
但是,如果现在有两个(多个)消费者线程和生产者线程,并且我们在生产者类的run方法中添加一个sleep()方法的执行,情况会如何呢?
运行程序:
产品数量出现了负数,这肯定是错误的。错误的原因在于,当一个消费者线程遇到产品为0时,等待,并释放锁标志,然后另外一个消费者线程获取到该锁标志,由于产品仍然为0,也等待,并释放锁标志。这时候,生产者线程获取到锁,在生产一个产品后,执行notifyAll()唤醒所有线程,这时候,一个消费者线程消费一个产品使得产品为0,另外一个消费者线程再消费一个产品使得产品变为了负数,这种现象称为虚假唤醒。在Object.wait()方法的javadoc中叙述了该如何解决这种问题:
即,将get和sale方法中的if都改为while,这样,每次被唤醒后,都会再次判断产品数是否>=0:
运行程序,发现结果终于正常了:
package concurrent; //店员类 class Clerk { private int product = 0; // 进货 public synchronized void get() { if (product >= 5) { System.out.println("产品已满!"); } else { 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); } } } // 生产者类 class Productor implements Runnable { private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { clerk.get(); } } } //消费者类 class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { clerk.sale(); } } } public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor productor = new Productor(clerk); Consumer consumer = new Consumer(clerk); new Thread(productor,"Productor A").start(); new Thread(consumer,"Consumer B").start(); } }
运行程序,结果如下:
这是一种不好的情况,因为当产品已满时,还在不停地生产,当缺货时,还在不停地消费。为此,我们引入等待唤醒机制:
package concurrent; //店员类 class Clerk { private int product = 0; // 进货 public synchronized void get() { if (product >= 5) { System.out.println("产品已满!"); //等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println(Thread.currentThread().getName() + ":" + ++product); //唤醒 this.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); //唤醒 this.notifyAll(); } } } // 生产者类 class Productor implements Runnable { private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { clerk.get(); } } } //消费者类 class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { clerk.sale(); } } } public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor productor = new Productor(clerk); Consumer consumer = new Consumer(clerk); new Thread(productor,"Productor A").start(); new Thread(consumer,"Consumer B").start(); } }
再运行程序,就不会再出现上述的情况:
但是,现在,我们将产品的囤积上限设定为1(这种情况在现实中也是有可能出现的):
然后运行程序:
程序的输出貌似没有问题,但请注意图中箭头所指的地方,这表示程序没有结束,还一直在执行。这是因为,当循坏到最后一轮时,由于产品已满引发了wait()操作,然后生产者线程等待,随后消费者消费了一份产品,并唤醒等待的生产者线程,此时,被唤醒的生产者线程由于循环结束,直接结束了线程的执行,但是另一边,消费者线程没有结束,而且由于将产品消费完后再次进入了等待,但是生产者线程此时已经结束了,不能再唤醒消费者线程,所以便进入了死循环。
解决这种问题的方法时去掉Clerk类中get方法和sale方法的else,并将原来else中的代码直接提出,这样,就算线程结束,也会先再次唤醒等待的线程:
package concurrent; //店员类 class Clerk { private int product = 0; // 进货 public synchronized void get() { if (product >= 1) { System.out.println("产品已满!"); // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + ++product); // 唤醒 this.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); // 唤醒 this.notifyAll(); } } // 生产者类 class Productor implements Runnable { private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { clerk.get(); } } } // 消费者类 class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { clerk.sale(); } } } public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor productor = new Productor(clerk); Consumer consumer = new Consumer(clerk); new Thread(productor, "Productor A").start(); new Thread(consumer, "Consumer B").start(); } }
运行程序,不再死循环:
但是,如果现在有两个(多个)消费者线程和生产者线程,并且我们在生产者类的run方法中添加一个sleep()方法的执行,情况会如何呢?
package concurrent; //店员类 class Clerk { private int product = 0; // 进货 public synchronized void get() { if (product >= 1) { System.out.println("产品已满!"); // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + ++product); // 唤醒 this.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); // 唤醒 this.notifyAll(); } } // 生产者类 class Productor implements Runnable { private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } clerk.get(); } } } // 消费者类 class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { clerk.sale(); } } } public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor productor = new Productor(clerk); Consumer consumer = new Consumer(clerk); new Thread(productor, "Productor A").start(); new Thread(consumer, "Consumer B").start(); new Thread(productor, "Productor C").start(); new Thread(consumer, "Consumer D").start(); } }
运行程序:
产品数量出现了负数,这肯定是错误的。错误的原因在于,当一个消费者线程遇到产品为0时,等待,并释放锁标志,然后另外一个消费者线程获取到该锁标志,由于产品仍然为0,也等待,并释放锁标志。这时候,生产者线程获取到锁,在生产一个产品后,执行notifyAll()唤醒所有线程,这时候,一个消费者线程消费一个产品使得产品为0,另外一个消费者线程再消费一个产品使得产品变为了负数,这种现象称为虚假唤醒。在Object.wait()方法的javadoc中叙述了该如何解决这种问题:
即,将get和sale方法中的if都改为while,这样,每次被唤醒后,都会再次判断产品数是否>=0:
package concurrent; //店员类 class Clerk { private int product = 0; // 进货 public synchronized void get() { while (product >= 1) { System.out.println("产品已满!"); // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + ++product); // 唤醒 this.notifyAll(); } // 售货 public synchronized void sale() { while (product <= 0) { System.out.println("缺货!"); // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + --product); // 唤醒 this.notifyAll(); } } // 生产者类 class Productor implements Runnable { private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } clerk.get(); } } } // 消费者类 class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { clerk.sale(); } } } public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor productor = new Productor(clerk); Consumer consumer = new Consumer(clerk); new Thread(productor, "Productor A").start(); new Thread(consumer, "Consumer B").start(); new Thread(productor, "Productor C").start(); new Thread(consumer, "Consumer D").start(); } }
运行程序,发现结果终于正常了:
相关文章推荐
- Java多线程生产者消费者说明等待唤醒机制问题和虚假唤醒问题
- 多线程-等待唤醒机制经典案例-生产者消费者
- Android(java)学习笔记71:生产者和消费者之等待唤醒机制
- java多线程中的生产者与消费者之等待唤醒机制@Version2.0
- 多线程_生产者消费者之等待唤醒机制代码优化
- 多线程-生产者消费者之等待唤醒机制
- 多线程_生产者消费者之等待唤醒机制思路图解
- java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)
- 23.生产者消费者案例-虚假唤醒
- 线程的等待唤醒机制(生产者消费者代码)
- java多线程中的等待唤醒机制--多生产者多消费者问题
- java多线程 生产者消费者案例-虚假唤醒
- 多线程 等待唤醒机制 生产者消费者 (Lock jdk1.5版)
- 多线程——等待唤醒机制经典实例:生产者消费者模式
- 线程间通信、等待唤醒机制、生产者消费者问题(Lock,Condition)、停止线程和守护线程、线程优先级
- 生产者和消费者之等待唤醒机制
- 线程间通信:生产者消费者(等待唤醒机制)
- 多线程_生产者消费者之等待唤醒机制代码分析
- 多线程-生产者消费者之等待唤醒机制代码优化
- java多线程之 生产者和消费者 线程间通信 等待与唤醒机制