通俗的解释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. }  
原文地址:https://www.cnblogs.com/sa-dan/p/6837156.html