• Java线程间通信之wait/notify


      Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式。先来我们来看下相关定义:

        wait() :调用该方法的线程进入WATTING状态,只有等待另外线程的通知或中断才会返回,调用wait()方法后,会释放对象的锁。

        wait(long):超时等待最多long毫秒,如果没有通知就超时返回。

        notify() : 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁。

        notifyAll():通知所有等待在该对象上的线程。

    一个小例子

      我们来模拟个简单的例子来说明,我们楼下有个小小的饺子馆,生意火爆,店里有一个厨师,一个服务员,为避免厨师每做好一份,服务员端出去一份,效率太低且浪费体力。现假设厨师每做好10份,服务员就用一个大木盘子端给客户,每天卖够100份就打烊收工,厨师服务员各自回家休息。

      思考一下,要实现该功能,如果不使用等待/通知机制,那么最直接的方式可能就是,服务员隔一段时间去厨房看看,满10份就用盘子端出去。这种方式有两个很大的弊病:

      1.如果服务员去厨房看的太勤快,服务员太累了,这样还不如每做一碗就端一碗给客人,大木盘子的作用就体现不出来了。具体表现在实现代码层面就是:需要不断的循环,浪费处理器资源。

      2.如果服务员隔很久才去厨房看一下,就无法确保及时性了,可能厨师早都做够10份了,服务员却没观察到。

      针对上面这个例子,使用等待/通知机制就合理的多了,厨师每做够10份,就喊一声“饺子好了,可以端走啦”。服务员收到通知,就去厨房将饺子端给客人;厨师还没做够,即还没收到厨师的通知,就可以稍微休息下,但也得竖起耳朵等候厨师的通知。 

     

     1 package ConcurrentTest;
     2 
     3 import thread.BlockQueue;
     4 
     5 /**
     6  * Created by chengxiao on 2017/6/17.
     7  */
     8 public class JiaoziDemo {
     9     //创建个共享对象做监视器用
    10     private static Object obj = new Object();
    11     //大木盘子,一盘最多可盛10份饺子,厨师做满10份,服务员就可以端出去了。
    12     private static Integer platter = 0;
    13     //卖出的饺子总量,卖够100份就打烊收工
    14     private static Integer count = 0;
    15 
    16     /**
    17      * 厨师
    18      */
    19     static class Cook implements Runnable{
    20         @Override
    21         public void run() {
    22             while(count<100){
    23                 synchronized (obj){
    24                     while (platter<10){
    25                         platter++;
    26                     }
    27                     //通知服务员饺子好了,可以端走了
    28                     obj.notify();
    29                     System.out.println(Thread.currentThread().getName()+"--饺子好啦,厨师休息会儿");
    30                 }
    31                 try {
    32                     //线程睡一会,帮助服务员线程抢到对象锁
    33                     Thread.sleep(100);
    34                 } catch (InterruptedException e) {
    35                     e.printStackTrace();
    36                 }
    37             }
    38             System.out.println(Thread.currentThread().getName()+"--打烊收工,厨师回家");
    39         }
    40     }
    41 
    42     /**
    43      * 服务员
    44      */
    45     static class Waiter implements Runnable{
    46         @Override
    47         public void run() {
    48             while(count<100){
    49                 synchronized (obj){
    50                     //厨师做够10份了,就可以端出去了
    51                     while(platter < 10){
    52                         try {
    53                             System.out.println(Thread.currentThread().getName()+"--饺子还没好,等待厨师通知...");
    54                             obj.wait();
    55                             BlockQueue
    56                         } catch (InterruptedException e) {
    57                             e.printStackTrace();
    58                         }
    59                     }
    60                     //饺子端给客人了,盘子清空
    61                     platter-=10;
    62                     //又卖出去10份。
    63                     count+=10;
    64                     System.out.println(Thread.currentThread().getName()+"--服务员把饺子端给客人了");
    65                 }
    66             }
    67             System.out.println(Thread.currentThread().getName()+"--打烊收工,服务员回家");
    68 
    69         }
    70     }
    71     public static void main(String []args){
    72         Thread cookThread = new Thread(new Cook(),"cookThread");
    73         Thread waiterThread = new Thread(new Waiter(),"waiterThread");
    74         cookThread.start();
    75         waiterThread.start();
    76     }
    77 }
    一个小例子

    运行结果

    cookThread--饺子好啦,厨师休息会儿
    waiterThread--服务员把饺子端给客人了
    waiterThread--饺子还没好,等待厨师通知...
    cookThread--饺子好啦,厨师休息会儿
    waiterThread--服务员把饺子端给客人了
    waiterThread--饺子还没好,等待厨师通知...
    cookThread--饺子好啦,厨师休息会儿
    waiterThread--服务员把饺子端给客人了
    waiterThread--饺子还没好,等待厨师通知...
    cookThread--饺子好啦,厨师休息会儿
    waiterThread--服务员把饺子端给客人了
    waiterThread--饺子还没好,等待厨师通知...
    cookThread--饺子好啦,厨师休息会儿
    waiterThread--服务员把饺子端给客人了
    waiterThread--饺子还没好,等待厨师通知...
    cookThread--饺子好啦,厨师休息会儿
    waiterThread--服务员把饺子端给客人了
    waiterThread--饺子还没好,等待厨师通知...
    cookThread--饺子好啦,厨师休息会儿
    waiterThread--服务员把饺子端给客人了
    waiterThread--饺子还没好,等待厨师通知...
    cookThread--饺子好啦,厨师休息会儿
    waiterThread--服务员把饺子端给客人了
    waiterThread--饺子还没好,等待厨师通知...
    cookThread--饺子好啦,厨师休息会儿
    waiterThread--服务员把饺子端给客人了
    waiterThread--饺子还没好,等待厨师通知...
    cookThread--饺子好啦,厨师休息会儿
    waiterThread--服务员把饺子端给客人了
    waiterThread--打烊收工,服务员回家
    cookThread--打烊收工,厨师回家
    运行结果

     运行机制

    借用《并发编程的艺术》中的一张图来了解下wait/notify的运行机制

    可能有人会对所谓监视器(monitor),对象锁(lock)不甚了解,在此简单解释下:

      jvm为每一个对象和类都关联一个锁,锁住了一个对象,就是获得了对象相关联的监视器。

      只有获取到对象锁,才能拿到监视器,如果获取锁失败了,那么线程就会进入阻塞队列中;如果成功拿到对象锁,也可以使用wait()方法,在监视器上等待,此时会释放锁,并进入等地队列中。

      关于锁和监视器的区别,园子里有个哥们的文章写得很详细透彻,在此引用一下,有兴趣的童鞋可以了解一下锁和监视器之间的区别 - Java并发

    根据上面的图我们来理一下具体的过程

       1.首先,waitThread获取对象锁,然后调用wait()方法,此时,wait线程会放弃对象锁,同时进入对象的等待队列WaitQueue中;

       2.notifyThread线程抢占到对象锁,执行一些操作后,调用notify()方法,此时会将等待线程waitThread从等待队列WaitQueue中移到同步队列SynchronizedQueue中,waitThread由waitting状态变为blocked状态。需要注意的时,notifyThread此时并不会立即释放锁,它继续运行,把自己剩余的事儿干完之后才会释放锁;

      3.waitThread再次获取到对象锁,从wait()方法返回继续执行后续的操作;

      4.一个基于等待/通知机制的线程间通信的过程结束。

    至于notifyAll则是在第二步中将等待队列中的所有线程移到同步队列中去。

    避免踩坑

      在使用wait/notify/notifyAll时有一些特别留意的,在此再总结一下:

        1.一定在synchronized中使用wait()/notify()/notifyAll(),也就是说一定要先获取锁,这个前面我们讲过,因为只有加锁后,才能获得监视器。否则jvm也会抛出IllegalMonitorStateException异常。

        2.使用wait()时,判断线程是否进入wait状态的条件一定要使用while而不要使用if,因为等待线程可能会被错误地唤醒,所以应该使用while循环在等待前等待后都检查唤醒条件是否被满足,保证安全性。

        3.notify()或notifyAll()方法调用后,线程不会立即释放锁。调用只会将wait中的线程从等待队列移到同步队列,也就是线程状态从waitting变为blocked;

        4.从wait()方法返回的前提是线程重新获得了调用对象的锁。

    后记

      关于wait/notify的相关内容就介绍到此,在实际使用中,要特别留意上文中提到的几点,不过一般情况下,我们直接使用wait/notify/notifyAll去完成线程间通信,生产者/消费者模型的机会不多,因为Java并发包中已经提供了很多优秀精妙的工具,像各种BlockingQueue等等,后面有机会也会详细介绍的。

      共勉

  • 相关阅读:
    也谈谈关于WEB的感想
    spring boot,https,双向ssl认证
    Spring Cloud Gateway(二)
    Spring Cloud Gateway(一)
    .Net Web Service 自定义返回值命名
    随便记一下,C#并行环境操作Winform的代码段
    随便记一下,解决Windows Server 2012无法远程登录的方法
    记录C#控件DataGridView绑定BindingList无法排序问题(转)
    记录SQL Server 2019链接Oracle 11g R2的过程
    Json CPP 中文支持与入门示例
  • 原文地址:https://www.cnblogs.com/chengxiao/p/7043529.html
Copyright © 2020-2023  润新知