2-线程通知与等待

线程通知与等待

  • Java中Object类是所有类的父类,鉴于继承机制,Java把所有类都需要的方法放到了Object类里面。

  • wait()方法

    • 当一个线程调用一个共享变量的wait()方法的时候,该线程会被阻塞挂起,直到发生了下面几件事才返回:

      1. 其他线程调用了notify()方法或者notifyAll()方法(通知);

      2. 其他线程调用了该线程的interrupt()方法()(通知),该线程抛出InterruptException异常返回。

    • 注意,如果调用wait()方法的线程没有事先获取到该对象的监视器锁,则调用wait()方法时线程会抛出IllegalMonitorStateException(非法监视器状态异常)。

    • 那么一个线程怎么获取到一个共享变量的监视器锁呢?

      1. 执行synchronized同步代码块,使用该共享变量作为参数。

        synchronized(共享变量){
            //doSomething
        }
        
      2. 调用该共享变量的方法,并且使用synchronized修饰。

        synchronized void add(int a, int b){
            //doSomething
        }
        
    • 一个线程可以从挂起状态变为运行状态(也就是被唤醒),即使该线程并没有被其他线程调用notify(),allNotify()方法进行通知,或者被中断,或者等待超时,这就是虚假唤醒。

    • 虚假唤醒的做法是不停的去测试该线程被唤醒的条件是否满足,不满足则继续等待,也就是说在一个循环中调用wait()方法进行防范。退出循环的条件是满足了唤起线程的条件。

      synchronized (obj){
          while(条件不满足){
              obj.wait();
          }
      }
      
    • 生产者消费者模型

      //生产线程
      synchronized (queue){
          //消息队列已满,则等待队列空闲
          while(queue.size() == MAX_SIZE){
              try{
                  //挂起当前线程,并释放通过同步块获取的queue上的锁,让消费者线程可以获取到该锁,然后
                  //获取队列里的元素
                  queue.wait();
              } catch(Exception e){
                  e.printStackTrace();
              }
          }
          
          //空闲则生成元素,并通知消费者线程
          queue.add(e);
          queue.notifyAll();
      }
      
      //消费者线程
      synchronized (queue){
          //消费队列为空
          while(queue.size() == 0){
              try{
                  //挂起当前线程,并释放同步块上的queue共享锁,让生产者线程可以获取到该锁,
                  //将生产元素放入队列中
                  queue.wait();
              } catch(Exception e){
                  e.printStackTrace();
              }
          }
          
          //消费元素,并通知唤醒生产者线程
          queue.take();
          queue.notifyAll();
      }
      
    • 在上图代码中假如生产者A首先通过synchronized获取到了queue上的锁,那么后续所有企图生产元素的线程和消费者线程将会在获取该监视器锁的地方被阻塞挂起。线程A获取该锁发现队列已满的时候就会调用queue.wait()方法阻塞自己,然后释放获取到了queue上的锁,让其他的生产者线程和消费者线程其中的一个线程进入同步块。

    • 另外注意的是,当前线程调用共享变量的wain()方法后只会释放当前共享变量上的锁,如果当前线程上还有其他共享锁,则这些锁是不会被释放的。下面看一个例子:

      package com.heiye.temp;
      
      public class Temp {
      
          //创建资源
          private static volatile Object resourcesA = new Object();
          private static volatile Object resourcesB = new Object();
      
          public static void main(String[] args) throws InterruptedException {
              //创建线程
              Thread threadA = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      try {
                          //获取resourceA共享资源的监视器锁
                          synchronized (resourcesA) {
                              System.out.println("ThreadA get resourceA lock");
                              //获取资源resourcesB共享资源的锁
                              synchronized (resourcesB) {
                                  System.out.println("ThreadA get resourceB lock");
                                  //线程A阻塞,并释放resourceA资源的锁
                                  System.out.println("ThreadA release resourceA lock");
                                  resourcesA.wait();
                              }
                          }
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
              });
      
              //创建线程B
              Thread threadB=new Thread(new Runnable() {
                  @Override
                  public void run() {
                      try{
                          //休眠1s
                          Thread.sleep(1000);
                          //获取resourceA共享资源的监控锁
                          synchronized (resourcesA){
                              System.out.println("ThreadB get resourceA lock");
                              System.out.println("ThreadB try get resourceB lock...");
                              //获取资源B的共享监控锁
                              synchronized (resourcesB){
                                  System.out.println("ThreadB get resourceB lock");
                                  //线程B阻塞,并释放获取到的资源A的锁
                                  System.out.println("threadB release resourceA lock");
                                  resourcesA.wait();
                              }
                          }
      
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              });
      
              threadA.start();
              threadB.start();
      
              //等待两个线程结束
              threadA.join();
              threadB.join();
              System.out.println("main over");
          }
      }
      
    • image

    • 在如上代码中,在main函数中启动了两个线程A和B,为了让A线程先获取到锁,首先让B睡眠1秒,那么B就会阻塞,线程A就优先获取到了资源A和资源B上的锁,然后调用A资源的wait()方法阻塞自己然后自己身上A资源的锁。B经过休眠后会尝试进入A资源上的锁,如果线程A当时还没有调用wait()方法释放锁,那么线程B就会阻塞,当线程A释放掉A资源的锁后,线程B就会获取到A资源的锁,然后B线程就会尝试获取B资源的锁,但是线程A并没有释放B资源的锁,因此线程B就会一直等待A释放B资源的锁。所以线程B在尝试获取B资源的锁上一直会阻塞。这就证明了当线程调用共享对象的wait()方法的时,当前线程只会释放当前共享对象的锁,当前线程持有的其他对象的监控锁就不会被释放。

    • 当一个线程调用共享对象的wait()方法阻塞挂起后,如果其他线程中断了该线程,则该线程就会抛出InterruptedException异常并返回。

      package com.heiye.temp;
      
      public class WaitNotifyInterrupt {
          private static Object obj=new Object();
      
          public static void main(String[] args) throws InterruptedException {
              //创建线程
              Thread threadA=new Thread(new Runnable() {
                  @Override
                  public void run() {
                      try {
                          System.out.println("---begin---");
                          //阻塞当前线程
                          synchronized (obj){
                              obj.wait();
                          }
      
                          System.out.println("---end---");
                      } catch (Exception e){
                          e.printStackTrace();
                      }
                  }
              });
      
              threadA.start();
              Thread.sleep(1000);
              System.out.println("---begin interrupt threadA---");
              threadA.interrupt();
              System.out.println("---end interrupt threadA---");
          }
      }
      

      image-20210922170527926

    • 在如上代码中,threadA调用共享对象obg的wait()方法后阻塞挂起了自己,然后主程序在休眠1s后打断了threadA的线程,中断后threadA在obj.wait()处抛出了InterruptedException异常并返回终止。

  • wait(long timeout)方法

    • 该方法相比wait()方法多了一个超时参数,他的不同之处在于,如果一个线程调用共享对象的该方法挂起后,没有指定的timeout ms时间内被其他线程调用该共享变量的notify()或者notifyAll()方法唤醒,那么该函数还是因为超时而返回。如果将timeout设置为0就和wait()一样,如果传递一个负的timeout则会报IllegalArgumantException异常。
  • wait(Long timeout, int nanos)

    • 当nacos>0的时候,timeout+1;

      //源码
      public final void wait(long timeout, int nanos) throws InterruptedException {
              if (timeout < 0) {
                  throw new IllegalArgumentException("timeout value is negative");
              }
      
              if (nanos < 0 || nanos > 999999) {
                  throw new IllegalArgumentException(
                                      "nanosecond timeout value out of range");
              }
      
              if (nanos > 0) {
                  timeout++;
              }
      
              wait(timeout);
          }
      
  • notify()方法

    • 一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait()方法后挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪一个线程是随机的。
    • 此外,被唤醒的线程不能马上从wait()方法中返回并继续执行,他必须在获取了共享对象的监控锁之后才可以返回,也就是唤醒它的线程释放了共享变量的监视器后,被唤醒的线程也不一定会获得共享对象的监视锁。这是因为该线程还需要和其它线程一起竞争该锁,只有该线程竞争到了共享变量的监视锁之后才可以继续工作。
    • 类似wait()方法,只有当前线程获取到了共享变量的监视锁之后,才可以调用共享变量的notify()方法。否则会报IllegalMoniorStateException异常。
  • notify()方法

    • 不同于在共享变量上调用notify()方法会唤醒阻塞到该共享变量上的一个线程,notify()会唤醒所有在该共享变量由于调用wait()系列方法而被挂起的进程。

      package com.heiye.temp;
      
      public class Temp3 {
          private static volatile Object resourceA = new Object();
      
          public static void main(String[] args) throws InterruptedException {
              //创建线程
              Thread threadA = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      //获取资源A的锁
                      synchronized (resourceA) {
                          System.out.println("threadA get resourceA lock");
                          try {
                              System.out.println("threadA begin wait");
                              resourceA.wait();
                              System.out.println("threadA end wait");
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              });
      
              //创建线程
              Thread threadB = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      synchronized (resourceA) {
                          System.out.println("threadB get resourceA lock");
                          try {
                              synchronized (resourceA) {
                                  System.out.println("threadB begin wait");
                                  resourceA.wait();
                                  System.out.println("threadB end wait");
                              }
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              });
      
              //创建线程
              Thread threadC = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      synchronized (resourceA) {
                          System.out.println("threadC begin notify");
                          resourceA.notify();
                      }
                  }
              });
      
              //启动线程
              threadA.start();
              threadB.start();
              Thread.sleep(1000);
              threadC.start();
      
              threadA.join();
              threadB.join();
              threadC.join();
              System.out.println("main over");
          }
      }
      

      image

    • 如上代码开启了三个线程,其中线程A和B分别调用了共享资源resourceA的wait()方法,线程C则调用了notify()方法。这里执行线程C之前时候首先让主线程睡眠1s,目的是为了让线程A和线程B全部执行到调用wait()方法之后再调用到notify()方法,这个例子试图在线程A和线程B都因为调用资源A的wait()方法之后而被阻塞后,让线程C再调用A资源的notify()方法,从而唤醒线程A和B,但实际上只唤醒了A线程。B没有被唤醒。

    • 当多个线程阻塞后,共享资源有一个阻塞集合,谁优先被阻塞,哪个阻塞的进程就会为该进程存放到集合中,等待通知后,会首先通知。

    • 把notify()改为notifyAll()后如下

      package com.heiye.temp;
      
      public class Temp3 {
          private static volatile Object resourceA = new Object();
      
          public static void main(String[] args) throws InterruptedException {
              //创建线程
              Thread threadA = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      //获取资源A的锁
                      synchronized (resourceA) {
                          System.out.println("threadA get resourceA lock");
                          try {
                              System.out.println("threadA begin wait");
                              resourceA.wait();
                              System.out.println("threadA end wait");
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              });
      
              //创建线程
              Thread threadB = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      synchronized (resourceA) {
                          System.out.println("threadB get resourceA lock");
                          try {
                              synchronized (resourceA) {
                                  System.out.println("threadB begin wait");
                                  resourceA.wait();
                                  System.out.println("threadB end wait");
                              }
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              });
      
              //创建线程
              Thread threadC = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      synchronized (resourceA) {
                          System.out.println("threadC begin notify");
                          resourceA.notifyAll();
                      }
                  }
              });
      
              //启动线程
              threadA.start();
              threadB.start();
              Thread.sleep(1000);
              threadC.start();
      
              threadA.join();
              threadB.join();
              threadC.join();
              System.out.println("main over");
          }
      }
      

      image

posted @ 2021-09-22 18:45  LilyFlower  阅读(56)  评论(0编辑  收藏  举报