星空网 > 软件开发 > Java

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

首先引入下面这段生产者和消费者的程序,店员类作为生产产品和消费产品的中介,其中的数据product为共享数据,产品最多只能囤积5个,当产品达到5个还在生产时,就会提示“产品已满!”,类似地,如果产品只有0个了还在消费,会提示“缺货!”:

 1 package concurrent; 2  3 //店员类 4 class Clerk { 5   private int product = 0; 6  7   // 进货 8   public synchronized void get() { 9     if (product >= 5) {10       System.out.println("产品已满!");11     } else {12       System.out.println(Thread.currentThread().getName() + ":" + ++product);13     }14   }15 16   // 售货17   public synchronized void sale() {18     if (product <= 0) {19       System.out.println("缺货!");20     } else {21       System.out.println(Thread.currentThread().getName() + ":" + --product);22     }23   }24 }25 26 // 生产者类27 class Productor implements Runnable {28 29   private Clerk clerk;30 31   public Productor(Clerk clerk) {32     this.clerk = clerk;33   }34 35   @Override36   public void run() {37     for (int i = 0; i < 10; i++) {38       clerk.get();39     }40 41   }42 }43 44 //消费者类45 class Consumer implements Runnable {46 47   private Clerk clerk;48 49   public Consumer(Clerk clerk) {50     this.clerk = clerk;51   }52 53   @Override54   public void run() {55     for (int i = 0; i < 10; i++) {56       clerk.sale();57     }58   }59 }60 61 public class TestProductorAndConsumer {62 63   public static void main(String[] args) {64     Clerk clerk = new Clerk();65 66     Productor productor = new Productor(clerk);67     Consumer consumer = new Consumer(clerk);68 69     new Thread(productor,"Productor A").start();70     new Thread(consumer,"Consumer B").start();71   }72 }

运行程序,结果如下: 

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

这是一种不好的情况,因为当产品已满时,还在不停地生产,当缺货时,还在不停地消费。为此,我们引入等待唤醒机制:

 1 package concurrent; 2  3 //店员类 4 class Clerk { 5   private int product = 0; 6  7   // 进货 8   public synchronized void get() { 9     if (product >= 5) {10       System.out.println("产品已满!");11 12       //等待13       try {14         this.wait();15       } catch (InterruptedException e) {16         e.printStackTrace();17       }18     } else {19       System.out.println(Thread.currentThread().getName() + ":" + ++product);20       //唤醒21       this.notifyAll();22     }23   }24 25   // 售货26   public synchronized void sale() {27     if (product <= 0) {28       System.out.println("缺货!");29       //等待30       try {31         this.wait();32       } catch (InterruptedException e) {33         e.printStackTrace();34       }35     } else {36       System.out.println(Thread.currentThread().getName() + ":" + --product);37       //唤醒38       this.notifyAll();39     }40   }41 }42 43 // 生产者类44 class Productor implements Runnable {45 46   private Clerk clerk;47 48   public Productor(Clerk clerk) {49     this.clerk = clerk;50   }51 52   @Override53   public void run() {54     for (int i = 0; i < 10; i++) {55       clerk.get();56     }57   }58 }59 60 //消费者类61 class Consumer implements Runnable {62 63   private Clerk clerk;64 65   public Consumer(Clerk clerk) {66     this.clerk = clerk;67   }68 69   @Override70   public void run() {71     for (int i = 0; i < 10; i++) {72       clerk.sale();73     }74 75   }76 }77 78 public class TestProductorAndConsumer {79 80   public static void main(String[] args) {81     Clerk clerk = new Clerk();82 83     Productor productor = new Productor(clerk);84     Consumer consumer = new Consumer(clerk);85 86     new Thread(productor,"Productor A").start();87     new Thread(consumer,"Consumer B").start();88   }89 }

再运行程序,就不会再出现上述的情况: 

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

但是,现在,我们将产品的囤积上限设定为1(这种情况在现实中也是有可能出现的): 

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

然后运行程序:

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

程序的输出貌似没有问题,但请注意图中箭头所指的地方,这表示程序没有结束,还一直在执行。这是因为,当循坏到最后一轮时,由于产品已满引发了wait()操作,然后生产者线程等待,随后消费者消费了一份产品,并唤醒等待的生产者线程,此时,被唤醒的生产者线程由于循环结束,直接结束了线程的执行,但是另一边,消费者线程没有结束,而且由于将产品消费完后再次进入了等待,但是生产者线程此时已经结束了,不能再唤醒消费者线程,所以便进入了死循环。 

解决这种问题的方法时去掉Clerk类中get方法和sale方法的else,并将原来else中的代码直接提出,这样,就算线程结束,也会先再次唤醒等待的线程:

 1 package concurrent; 2  3 //店员类 4 class Clerk { 5   private int product = 0; 6  7   // 进货 8   public synchronized void get() { 9     if (product >= 1) {10       System.out.println("产品已满!");11 12       // 等待13       try {14         this.wait();15       } catch (InterruptedException e) {16         e.printStackTrace();17       }18     }19     System.out.println(Thread.currentThread().getName() + ":" + ++product);20     // 唤醒21     this.notifyAll();22   }23 24   // 售货25   public synchronized void sale() {26     if (product <= 0) {27       System.out.println("缺货!");28       // 等待29       try {30         this.wait();31       } catch (InterruptedException e) {32         e.printStackTrace();33       }34     }35     System.out.println(Thread.currentThread().getName() + ":" + --product);36     // 唤醒37     this.notifyAll();38   }39 }40 41 // 生产者类42 class Productor implements Runnable {43 44   private Clerk clerk;45 46   public Productor(Clerk clerk) {47     this.clerk = clerk;48   }49 50   @Override51   public void run() {52     for (int i = 0; i < 10; i++) {53       clerk.get();54     }55   }56 }57 58 // 消费者类59 class Consumer implements Runnable {60 61   private Clerk clerk;62 63   public Consumer(Clerk clerk) {64     this.clerk = clerk;65   }66 67   @Override68   public void run() {69     for (int i = 0; i < 10; i++) {70       clerk.sale();71     }72   }73 }74 75 public class TestProductorAndConsumer {76 77   public static void main(String[] args) {78     Clerk clerk = new Clerk();79 80     Productor productor = new Productor(clerk);81     Consumer consumer = new Consumer(clerk);82 83     new Thread(productor, "Productor A").start();84     new Thread(consumer, "Consumer B").start();85   }86 }

运行程序,不再死循环: 

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

但是,如果现在有两个(多个)消费者线程和生产者线程,并且我们在生产者类的run方法中添加一个sleep()方法的执行,情况会如何呢?

 1 package concurrent; 2  3 //店员类 4 class Clerk { 5   private int product = 0; 6  7   // 进货 8   public synchronized void get() { 9     if (product >= 1) {10       System.out.println("产品已满!");11 12       // 等待13       try {14         this.wait();15       } catch (InterruptedException e) {16         e.printStackTrace();17       }18     }19     System.out.println(Thread.currentThread().getName() + ":" + ++product);20     // 唤醒21     this.notifyAll();22   }23 24   // 售货25   public synchronized void sale() {26     if (product <= 0) {27       System.out.println("缺货!");28       // 等待29       try {30         this.wait();31       } catch (InterruptedException e) {32         e.printStackTrace();33       }34     }35     System.out.println(Thread.currentThread().getName() + ":" + --product);36     // 唤醒37     this.notifyAll();38   }39 }40 41 // 生产者类42 class Productor implements Runnable {43 44   private Clerk clerk;45 46   public Productor(Clerk clerk) {47     this.clerk = clerk;48   }49 50   @Override51   public void run() {52     for (int i = 0; i < 10; i++) {53       try {54         Thread.sleep(100);55       } catch (InterruptedException e) {56         // TODO Auto-generated catch block57         e.printStackTrace();58       }59       clerk.get();60     }61   }62 }63 64 // 消费者类65 class Consumer implements Runnable {66 67   private Clerk clerk;68 69   public Consumer(Clerk clerk) {70     this.clerk = clerk;71   }72 73   @Override74   public void run() {75     for (int i = 0; i < 10; i++) {76       clerk.sale();77     }78   }79 }80 81 public class TestProductorAndConsumer {82 83   public static void main(String[] args) {84     Clerk clerk = new Clerk();85 86     Productor productor = new Productor(clerk);87     Consumer consumer = new Consumer(clerk);88 89     new Thread(productor, "Productor A").start();90     new Thread(consumer, "Consumer B").start();91     new Thread(productor, "Productor C").start();92     new Thread(consumer, "Consumer D").start();93   }94 }

运行程序: 

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

产品数量出现了负数,这肯定是错误的。错误的原因在于,当一个消费者线程遇到产品为0时,等待,并释放锁标志,然后另外一个消费者线程获取到该锁标志,由于产品仍然为0,也等待,并释放锁标志。这时候,生产者线程获取到锁,在生产一个产品后,执行notifyAll()唤醒所有线程,这时候,一个消费者线程消费一个产品使得产品为0,另外一个消费者线程再消费一个产品使得产品变为了负数,这种现象称为虚假唤醒。在Object.wait()方法的javadoc中叙述了该如何解决这种问题:

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

即,将get和sale方法中的if都改为while,这样,每次被唤醒后,都会再次判断产品数是否>=0:

 1 package concurrent; 2  3 //店员类 4 class Clerk { 5   private int product = 0; 6  7   // 进货 8   public synchronized void get() { 9     while (product >= 1) {10       System.out.println("产品已满!");11 12       // 等待13       try {14         this.wait();15       } catch (InterruptedException e) {16         e.printStackTrace();17       }18     }19     System.out.println(Thread.currentThread().getName() + ":" + ++product);20     // 唤醒21     this.notifyAll();22   }23 24   // 售货25   public synchronized void sale() {26     while (product <= 0) {27       System.out.println("缺货!");28       // 等待29       try {30         this.wait();31       } catch (InterruptedException e) {32         e.printStackTrace();33       }34     }35     System.out.println(Thread.currentThread().getName() + ":" + --product);36     // 唤醒37     this.notifyAll();38   }39 }40 41 // 生产者类42 class Productor implements Runnable {43 44   private Clerk clerk;45 46   public Productor(Clerk clerk) {47     this.clerk = clerk;48   }49 50   @Override51   public void run() {52     for (int i = 0; i < 10; i++) {53       try {54         Thread.sleep(100);55       } catch (InterruptedException e) {56         // TODO Auto-generated catch block57         e.printStackTrace();58       }59       clerk.get();60     }61   }62 }63 64 // 消费者类65 class Consumer implements Runnable {66 67   private Clerk clerk;68 69   public Consumer(Clerk clerk) {70     this.clerk = clerk;71   }72 73   @Override74   public void run() {75     for (int i = 0; i < 10; i++) {76       clerk.sale();77     }78   }79 }80 81 public class TestProductorAndConsumer {82 83   public static void main(String[] args) {84     Clerk clerk = new Clerk();85 86     Productor productor = new Productor(clerk);87     Consumer consumer = new Consumer(clerk);88 89     new Thread(productor, "Productor A").start();90     new Thread(consumer, "Consumer B").start();91     new Thread(productor, "Productor C").start();92     new Thread(consumer, "Consumer D").start();93   }94 }

运行程序,发现结果终于正常了: 

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

 转载自:http://blog.csdn.net/xiangwanpeng/article/details/54973782




原标题:【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

关键词:

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。
相关文章
我的浏览记录
最新相关资讯
海外公司注册 | 跨境电商服务平台 | 深圳旅行社 | 东南亚物流