zoukankan      html  css  js  c++  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     @Override
    36     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     @Override
    54     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     @Override
    53     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     @Override
    70     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     @Override
    51     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     @Override
    68     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     @Override
    51     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 block
    57                 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     @Override
    74     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     @Override
    51     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 block
    57                 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     @Override
    74     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

  • 相关阅读:
    mybatis映射文件配置-1对多
    启动服务器加载spring.xml:web.xml配置
    spring-mybatis.xml配置文件
    json转换器映射文件配置
    Restful levels
    事务隔离级别「脏读、不可重复读、幻读」
    Java CDI
    session和Cookie的介绍和应用
    java RE Validation常用
    hello2 source Analysis
  • 原文地址:https://www.cnblogs.com/ccfdod/p/6414695.html
Copyright © 2011-2022 走看看