【Java多线程与并发】——wait/notify

wait/notify为何而来?

我们知道,线程与线程之间并不是完全独立的个体,多个线程之间可以通过访问共享变量,可以实现线程间进行通信。然而当多个线程访问同一共享变量,如果没有使用同步机制,即没有使用synchronize同步方法或者同步代码块,我们不确定线程读到的共享变量的值到底是不是想要的,或者说是准确的值。因此等待/通知机制的出现就满足解决了这一需求问题。

 

wait与notify实现原理简介

每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后才会进入到就绪队列,等待CPU调度;反之如果一个线程被wait后,就会进入到阻塞队列,等待下一次被唤醒。

如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的阻塞队列中,阻塞队列中的线程会去竞争该对象锁。 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在阻塞队列中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时阻塞队列中的线程会继续竞争该对象锁。

wait()、notify()

方法简介: 

  • wait()
 public final void wait()  throws InterruptedException,IllegalMonitorStateException

       该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。进入wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时,没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch结构。

  • notify()
public final native void notify() throws IllegalMonitorStateException

        该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。

       该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知,并使它等待获取该对象的对象锁(notify后,当前线程不会马上释放该对象锁,wait所在的线程并不能马上获取该对象锁,要等到程序退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。这与下面的notifyAll()方法执行后的情况不同。

  • notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException

      该方法与notify()方法的工作方式相同,重要的一点差异是:

      notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

  • wait(long)和wait(long,int)

     显然,这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。

sleep和wait方法的区别

  • wait是Object的方法,而sleep是Thread特有的方法
  • wait和sleep方法都可以使得线程进入阻塞状态,都是可中断方法,被中断后都会收到中断异常
  • wait方法的执行必须在同步方法中进行,而sleep则不需要
  • 线程在同步方法中执行sleep方法时,不会释放monitor的锁,而wait方法则会释放monitor的锁

参考资料

【1】使用wait/notify/notifyAll实现线程间通信的几点重要说明

【2】《Java高并发编程详解》

【3】《Java并发编程的艺术》

 

你可能感兴趣的:(多线程与并发)