线程的通知与等待

  等待和通知包括wait()和notify()以及notifyAll()都属于Object中的方法,现在来一一学习一下。

wait()方法

  当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回:
  1、其他线程调用了该共享方法的notify()或者notifyAll()方法;
  2、其他线程调用了该线程的interrupt()方法,该线程抛出IntrruptedException异常返回;
  需要注意的是:如果调用wait()方法的线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程会抛出IllegalMonitorStateException异常。
  一个线程如何获取一个共享变量的监视器锁:
  1、执行sysnchorized同步代码块时,使用该共享变量作为参数,如:
       sychorized(共享变量){
          //doSomething
       }
  2、调用该共享变量的方法,并且该方法使用了sychorized修饰,如:
   		sychorized void add(int a,int b){
   		   //doSomething
   		}
   需要注意的是:一个线程可以从挂起状态变为可运行状态(也就是被唤醒),即使该线程没有被其他线程调用notify()、notifyAll()方法进行通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒,虽然虚假唤醒在应用实践中很少发生,但要防患于未然,做法就是不停地去测试该线程被唤醒的条件是否满足,不满足则继续等待,也就是说在一个循环中调用wait()方法进行防范。退出循环的条件是满足了唤醒该线程的条件,如下:
   sychorized(obj){
     while(条件不满足){
          obj.wait();
     }
  }
  
 其他要注意的问题:
 1、当前线程调用共享变量的wait()方法后之后释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的。
 2、当线程调用共享对象的wait()方法时,当前线程只会释放当前对象的锁,当前线程持有的其他共享的对象的监视器锁并不会被释放。
 3、当一个线程调用共享对象的wait()方法被阻塞挂起后,如果其他线程中断了该线程,则线程会抛出InterruptedException异常并返回。

wait(long timeout)方法

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

wait(long timeout,int nanos)方法

在其内部调用的是wait(long timeout)方法,只有nanos>0时才使参数timeout加1

notify()方法

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

notifyAll()方法

不同于在共享变量上调用notify()方法会唤醒被阻塞到该共享变量上的一个线程,notifyAll()方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。
要注意的是:在共享变量上调用notifyAll()方法只会唤醒调用这个方法前调用了wait系列方法而被放入共享变量等待集合里面的线程,如果调用notifyAll()方法后一个线程调用了该共享变量的wait()方法而被放入阻塞集合,则该线程是不会被唤醒的。

你可能感兴趣的:(Java并发编程之美学习笔记,Java)