zoukankan      html  css  js  c++  java
  • 通俗的解释JAVA wait/notify机制

    生活中,我们常遇到需要等待的场景,例如去银行办事,在没轮到自己之前需要一直等待,但是如果需要自己每隔几秒钟就去柜台前看看状况,那肯定是种非常低效和令人恼火的体验。而实际的情况是,接待员会让您拿个号,说"请稍等一会"(wait); 当排到时,语言和大屏幕会提示"请XXX号到N号柜台办理"(notify)。

    wait/notify机制也正是处理这样的场景:线程继续执行需要等待某个条件的变化,这个条件由另一个任务改变,如果一直空循环检查条件变化,是一种不良的CPU使用方式,这时候可以调用wait()将任务挂起,在其他线程调用了notify()或notifyAll()时,任务被唤醒并检查条件的变化。

    这个过程中,锁的持有发生了变化。介绍wait/notify最常用的例子是生产者和消费者,设想你去饭馆吃饭,叫来服务员说,把我的宫保鸡丁端上来吧。这时候你获得了服务员的锁,在解决你的事情前,服务员不能去做别的事。(同一时间,厨师可能已经做好了宫保鸡丁,等服务员来端,但是服务员在和你说话,厨师束手无策(等待锁)。)服务员没有宫保鸡丁,只能对你说:您稍等一下,我去厨房催催。服务员调用了wait()方法,你只好释放锁,服务员回到厨房,厨师怒气冲冲的喊(获得锁),宫保鸡丁好了,端走。

    下面用程序演示这一场景: 

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public class Waiter {  
    2.   
    3.     private String dishes = null;  
    4.       
    5.     public synchronized String getDishes() {  
    6.         System.out.printf("顾客获得服务员锁%n");  
    7.         while(this.dishes == null) {  
    8.             try {  
    9.                 System.out.printf("顾客取菜,没有菜...顾客线程等待(释放锁)%n");  
    10.                 wait();  
    11.             } catch(InterruptedException ex) {  
    12.                 ex.printStackTrace();  
    13.             }  
    14.         }  
    15.         String d = this.dishes;  
    16.         System.out.printf("顾客取走: %s%n", this.dishes);  
    17.         this.dishes = null;  
    18.         notifyAll();  
    19.         System.out.printf("服务员通知正在等待的线程%n");  
    20.         return d;  
    21.     }  
    22.       
    23.     public synchronized void setDishes(String dishes) {  
    24.         System.out.printf("厨师获得服务员锁%n");  
    25.         while(this.dishes != null) {  
    26.             try {  
    27.                 System.out.printf("厨师交菜,服务员已经端了另一份菜...厨师线程等待(释放锁)%n");  
    28.                 wait();  
    29.             } catch(InterruptedException ex) {  
    30.                 ex.printStackTrace();  
    31.             }  
    32.         }  
    33.         this.dishes = dishes;  
    34.         System.out.printf("厨师交菜: %s%n", this.dishes);  
    35.         notifyAll();  
    36.         System.out.printf("服务员通知正在等待的线程(顾客)%n");  
    37.     }  
    38.       
    39.     public static void main(String[] args) throws InterruptedException {  
    40.         Waiter busy = new Waiter();  
    41.         for(int i = 0; i < 10; i++) {  
    42.             Thread consumer = new Thread() {  
    43.                 public void run() {  
    44.                     busy.getDishes();  
    45.                 }  
    46.             };  
    47.             consumer.start();  
    48.         }  
    49.         Thread.sleep(100);  
    50.         for(int i = 0; i < 10; i++) {  
    51.             Thread chef = new Thread() {  
    52.                 public void run() {  
    53.                     String dishes = "宫保鸡丁";  
    54.                     busy.setDishes(dishes);  
    55.                 }  
    56.             };  
    57.             chef.start();  
    58.         }  
    59.     }  
    60. }  

    运行结果:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. 顾客获得服务员锁  
    2. 顾客取菜,没有菜...顾客线程等待(释放锁)  
    3. 厨师获得服务员锁  
    4. 厨师交菜: 宫保鸡丁  
    5. 服务员通知正在等待的线程(顾客)  
    6. 顾客取走: 宫保鸡丁  
    7. 服务员通知正在等待的线程(厨师)  

    下面来说明notifyAll的作用。

    修改下代码,把厨师和顾客都增加到10个

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public static void main(String[] args) {  
    2.     Waiter busy = new Waiter();  
    3.     for(int i = 0; i < 10; i++) {  
    4.         Thread consumer = new Thread() {  
    5.             public void run() {  
    6.                 busy.getDishes();  
    7.             }  
    8.         };  
    9.         consumer.start();  
    10.     }  
    11.     for(int i = 0; i < 10; i++) {  
    12.         Thread chef = new Thread() {  
    13.             public void run() {  
    14.                 String dishes = "宫保鸡丁";  
    15.                 busy.setDishes(dishes);  
    16.             }  
    17.         };  
    18.         chef.start();  
    19.     }  
    20. }  

    执行后会发现程序会陷入永久的等待无法结束,这是因为notify()方法只唤醒众多等待的线程中的一个,拿到菜后本应唤醒顾客取走,但是有可能随机唤醒了另一个等待的厨师,没有顾客能取走服务员手中的菜,这时候程序就无法继续下去了。

    解决的方法有两种:

    1 把notify()改成notifyAll(),唤醒所有等待的线程

    2 使用Java.util.concurrent库中的Condition,把等待的线程分为厨师和顾客两个集合,代码如下:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public class ConditionWaiter {  
    2.   
    3.     private String dishes = null;  
    4.     private Lock lock = new ReentrantLock();  
    5.     private Condition conConsumer = lock.newCondition();  
    6.     private Condition conChef = lock.newCondition();  
    7.       
    8.     public String getDishes() {  
    9.         try {  
    10.             lock.lock();  
    11.             System.out.printf("顾客获得服务员锁%n");  
    12.             while(this.dishes == null) {  
    13.                 try {  
    14.                     System.out.printf("顾客取菜,没有菜...顾客线程等待%n");  
    15.                     conConsumer.await();  
    16.                 } catch(InterruptedException ex) {  
    17.                     ex.printStackTrace();  
    18.                 }  
    19.             }  
    20.             String d = this.dishes;  
    21.             System.out.printf("顾客取走:%s%n", this.dishes);  
    22.             this.dishes = null;  
    23.             conChef.signal();  
    24.             System.out.printf("服务员通知正在等待的线程(厨师)%n");  
    25.             return d;  
    26.         } finally {  
    27.             lock.unlock();  
    28.         }  
    29.     }  
    30.       
    31.     public void setDishes(String dishes) {  
    32.         try {  
    33.             lock.lock();  
    34.             System.out.printf("厨师获得服务员锁%n");  
    35.             while(this.dishes != null) {  
    36.                 try {  
    37.                     System.out.printf("厨师交菜,服务员已经端了另一份菜...厨师线程等待%n");  
    38.                     conChef.await();  
    39.                 } catch(InterruptedException ex) {  
    40.                     ex.printStackTrace();  
    41.                 }  
    42.             }  
    43.             this.dishes = dishes;  
    44.             System.out.printf("厨师交菜:%s%n", this.dishes);  
    45.             conConsumer.signal();  
    46.             System.out.printf("服务员通知正在等待的线程(顾客)%n");  
    47.         } finally {  
    48.             lock.unlock();  
    49.         }  
    50.     }  
    51.       
    52.     public static void main(String[] args) throws InterruptedException {  
    53.         ConditionWaiter busy = new ConditionWaiter();  
    54.         for(int i = 0; i < 10; i++) {  
    55.             Thread consumer = new Thread() {  
    56.                 public void run() {  
    57.                     busy.getDishes();  
    58.                 }  
    59.             };  
    60.             consumer.start();  
    61.         }  
    62.         Thread.sleep(100);  
    63.         for(int i = 0; i < 10; i++) {  
    64.             Thread chef = new Thread() {  
    65.                 public void run() {  
    66.                     String dishes = "宫保鸡丁";  
    67.                     busy.setDishes(dishes);  
    68.                 }  
    69.             };  
    70.             chef.start();  
    71.         }  
    72.     }  
    73. }  


    事实上,wait/notify机制编程模型复杂也运行低效,通常我们应该采取更高级的类库实现类似场景。以下代码是使用BlockingQueue实现线程协作的示例:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
      1. public class BlockingQueueWaiter {  
      2.   
      3.     static BlockingQueue<String> queue = new ArrayBlockingQueue<>(1);  
      4.       
      5.     public static void main(String[] args) throws InterruptedException {  
      6.         for(int i = 0; i < 10; i++) {  
      7.             Thread consumer = new Thread() {  
      8.                 public void run() {  
      9.                     String dishes;  
      10.                     try {  
      11.                         System.out.printf("顾客尝试取菜%n");  
      12.                         dishes = queue.take();  
      13.                         System.out.printf("顾客取走:%s%n", dishes);  
      14.                     } catch (InterruptedException e) {  
      15.                         e.printStackTrace();  
      16.                     }  
      17.                       
      18.                 }  
      19.             };  
      20.             consumer.start();  
      21.         }  
      22.         Thread.sleep(100);  
      23.         for(int i = 0; i < 10; i++) {  
      24.             Thread chef = new Thread() {  
      25.                 public void run() {  
      26.                     String dishes = "宫保鸡丁";  
      27.                     try {  
      28.                         System.out.printf("厨师尝试交菜%n");  
      29.                         queue.put(dishes);  
      30.                         System.out.printf("厨师交菜:%s%n", dishes);  
      31.                     } catch (InterruptedException e) {  
      32.                         e.printStackTrace();  
      33.                     }  
      34.                       
      35.                 }  
      36.             };  
      37.             chef.start();  
      38.         }  
      39.     }  
      40. }  
  • 相关阅读:
    获取地址栏数据
    tag标签添加删除并把值存入到一个input的value内
    美化 input type=file控件
    高效的数组去重(js)
    原生js动态改变dom高度
    html5 postMessage解决跨域、跨窗口消息传递
    移动前端制作篇之javascript篇
    js中的事件委托
    图片轮播(定时播放)
    潭州课堂25班:Ph201805201 django 项目 第四十一课 后台 轮播图管理功能讲解,文档管理功能 实现 (课堂笔记)
  • 原文地址:https://www.cnblogs.com/sa-dan/p/6837156.html
Copyright © 2011-2022 走看看