• 全面理解线程间的通信方式


    因为存在对共享变量的操作,才有了讨论线程的话题。在线程中存在这样一种场景,一个线程操作了共享变量的值而另一个线程感知了此次操作,然后进行相应的操作。整个过程开始于一个线程,结束与另一个线程。前者我们可以称作生产者,后者我们可以称作消费者,因为有了前者的活动才出发后者逻辑的发生,这种隔离模式在功能实现方面具备了良好的伸缩性。等待/通知的java方法是任何对象都具备的,因为这些方法被定义在java的超类java.lang.Object上下图展示了Object 类的所有方法:

    等待、通知机制就是,线程A调用了对象Owait()方法进入等待状态,线程B 调用了对象Onotify()或者notifyAll() 唤醒处于对象O上等待的线程A,使线程A wait()方法处返回从而进行剩余操作。上述两个线程通过对象O来进行交互,而对象O上的wait()/notify()/notifyAll()就如同信号一般控制着线程的操作。这种例子生活中随处可见,例如商品入库,如果仓库中是满的我们就无法将商品再放入仓库,如果仓库中没有商品我们也无法从仓库中取出商品,再举一个日常发生在我们身边的场景。现在网购越来越方便,我们与快递之间有一个快递小哥在关联,快递小哥将包裹放到快递柜,我们去快递柜领取快递:

     1 /**
     2  * 快递
     3  */
     4 public class Courier extends Thread {
     5     
     6     @SneakyThrows
     7     @Override
     8     public void run() {
     9 
    10         synchronized (CourierCabinet.CABINET) {
    11             while (true) {
    12                 if (CourierCabinet.CABINET.size() == 10) {
    13                     //歇一歇吧 快递柜没地方了
    14                     CourierCabinet.CABINET.wait();
    15                 }
    16                 CourierCabinet.CABINET.add("包裹");
    17                 System.out.println("亲爱的顾客您的快递已入柜,请及时来领取");
    18                 CourierCabinet.CABINET.notify();
    19                 Thread.sleep(100);
    20 
    21             }
    22         }
    23 
    24     }
    25 }
     1 /**
     2  * 收件人
     3  */
     4 public class Recipient extends Thread {
     5 
     6 
     7     @SneakyThrows
     8     @Override
     9     public void run() {
    10 
    11         while (true) {
    12             synchronized (CourierCabinet.CABINET) {
    13 
    14                 if (CourierCabinet.CABINET.size() == 0) {
    15                     // 快递员还未将包裹入柜 没法领取等一等
    16                     CourierCabinet.CABINET.wait();
    17                 }
    18 
    19                 CourierCabinet.CABINET.remove("包裹");
    20                 System.out.println("哈哈 领到了我的快递...");
    21                 CourierCabinet.CABINET.notify();
    22                 Thread.sleep(100);
    23             }
    24         }
    25     }
    26 }
     1 **
     2  * 快递柜
     3  */
     4 public class CourierCabinet {
     5 
     6     /**
     7      * 快递柜容量
     8      */
     9     public static final List<String> CABINET = new ArrayList<>(10);
    10 
    11 
    12     public static void main(String[] args) {
    13 
    14 
    15         new Thread(new Courier()).start();
    16 
    17 
    18         new Thread(new Recipient()).start();
    19 
    20 
    21     }
    22 
    23 }

    运行结果:

    通过以上例子,可以总结出 使用对象的wait()/notify()/notifyAll()所要注意的点:

    1. 调用 wait()/notify()/notifyAll() 方法的线程必须是持有该对象的锁的线程
    2. 调用wait()方法后线程由RUNNING状态变为WAITING状态,并将当前线程放置在该对象的等待队列中,同时释放拥有的锁
    3. 调用notify()notifyAll()之后线程并不会立即从wait()方法处返回,而是需要等待调用notify()/notifyAll()的线程释放锁之后才会返回。
    4. notify() 将等待队列中的线程移动到同步队列中去,notifyAll()将等待队列中所有的线程移动到同步队列中去,此时被移动的线程状态由WAITING 转为BLOCKED

    关于线程同步、通知机制面试题

    1:为什么操作 wait() notify() notifyAll() 需要事先获取锁,

    主要是为了防止死锁了永久等待的发生,以上面的例子说明,收件人线程执行if(CABINET.size()==0)的时候满足条件 由于没有synchronize 加持,所以该线程并不一定会执行CABINET.wait() 可能被CPU切走了,线程进入了BLOCKED状态。

    此时快递小哥线程获取到了执行权,判断if(CABINET.size==10)不满足条件,然后执行CABINET.add(包裹操作)执行notify()因为收件人线程并没有执行wait(),所以就可能处于一直等待中。就如同你给我打电话 我还没有拿到电话你就已经打过了 此时我再拿到电话也不会收到你的电话了。

    Wait()放到synchronize 中执行就是为了保证线程安全,如果一个线程想要从wait()处返回也需要获取到该对象的锁否则会出现IllegalMonitorStateException异常。

    2:为什么线程通信的方法wait()notify()notifyAll()是定义在Object中而sleep()定义在线程类中?

    主要因为java中的wait()notify()notifyAll()都是锁级别的操作,操作这几个方法的线程必须持有该对象的锁,而锁又是属于对象的。每一个对象的对象头中有几位是标识锁的状态的,所以实际上锁是属于对象的并不是属于线程的。如果这几个方法定义在线程中会造成极大的不便,在实际的操作中我们会遇到一个线程获取几把锁的情况,如果将锁定义在线程中时间这种情况就不是那么的方便了。

    3wait()方法是属于对象的,那调用Thread.wait()会怎样?

    调用Thread.wait() 也就是说将Thread 当做锁对象,持有Thread对象的锁的线程在执行结束后会自动调用notify(),所以我们应该避免使用线程对象来作为锁对象。

    4notifyAll() 会唤醒所有的线程同时去争夺这把锁,如果没有获取到锁的对象该怎么办?

    没有抢到锁的线程会再次进入WAITING状态,进入对象的等待队列中去,直至有其他线程再次调用notify()或者notify()All 或者调用该线程的中断方法。

  • 相关阅读:
    对MySql查询缓存及SQL Server过程缓存的理解及总结
    PhpStorm中如何使用database工具,详细操作方法
    zookeeper 操作命令
    关于 php for zookeeper
    摘抄 <关于 作为>
    php 各种扩展
    http与tcp
    PHP 优化之php -fpm 进程
    MYSQL explain详解[转载]
    各种注释[转载]
  • 原文地址:https://www.cnblogs.com/yzjbk/p/12853063.html
Copyright © 2020-2023  润新知