关于wait/notify

Posted zjoe80

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于wait/notify相关的知识,希望对你有一定的参考价值。

一.wait/notify是什么

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()/notifyAll()方法,

线程A收到通知后退出等待队列,进入可运行状态,进而执行后续操作。

上诉两个线程通过对象O来完成交互,而对象上的wait()方法notify()/notifyAll()方法的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

 

二.wait/notify能做什么

线程与线程之间并不是完全独立的个体,多个线程之间可以通过访问共享变量,可以实现线程间进行通信。

然而当多个线程访问同一共享变量,如果没有使用同步机制,即没有使用synchronize同步方法或者同步代码块,

我们不确定线程读到的共享变量的值到底是不是想要的,或者说是准确的值。因此等待/通知机制的出现就满足解决了这一需求问题。

Java的wait/notify的通知机制可以用来实现线程间通信。wait表示线程的等待,调用该方法会导致线程阻塞,直至另一线程调用notify或notifyAll方法才可另其继续执行。

经典的生产者、消费者模式即是使用wait/notify机制得以完成。这两个方法主要用于多线程间的协同处理,即控制线程之间的等待、通知、切换及唤醒。

由于现在JAVA平台自身提升了很多并发类与库(JUC),以及很多第三方的开源的并发框架(disruptor、akka),因此大部分开发者都很少用这种原始的方法去控制线程的协作,

但是了解它们的原理对并发编码有一定的理解。

 

三.wait/notify原理

每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。

一个线程被唤醒后才会进入到就绪队列,等待CPU调度;反之如果一个线程被wait后,就会进入到阻塞队列,等待下一次被唤醒。

如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程),

被唤醒的的线程便会进入该对象的阻塞队列中,阻塞队列中的线程会去竞争该对象锁。

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在阻塞队列中,唯有线程再次调用wait()方法,它才会重新回到等待池中。

而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时阻塞队列中的线程会继续竞争该对象锁。

(1)调用wait() 首先会获取监视器锁,获得成功后,会让线程进入等待状态进入等待队列并且释放锁;

(2)然后当其他线程调用notify或者notifyall以后,会选择从等待队列中唤醒任意一个线程;

(3)而执行完notify方法以后,并不会立马唤醒线程,原因是当前线程仍然持有这把锁,处于等待状态的线程无法获得锁。

        必须要等到当前的线程执行完按monitorexit指令之后,也就是被释放之后,处于等待队列的线程就可以开始竞争锁了。

技术图片

对象锁的本质,实际上是对象头的一个监视器锁的数据结构。这个结构如下:技术图片

 几个线程一起竞争对象的锁(entry),只有一个能成功(acquire),成功的线程记录在The Owner结构中。调用wait、notify运行流程如下:

(1) 现有一个对象o,锁正在被线程 t1 持有,调用wait()方法后,线程 t1 将会被"晾到" (实际上仅仅是记录到) Wait Set 结构中。

(2)然后将会有另一个线程 t2 获取到锁,The Owner记录的变成了 t2 线程。

(3)t2 线程不需要 o的锁时,调用o.notify()/o.notifyAll()方法,对象o就会告诉 Wait Set结构中记录的线程们:你们又可以来竞争我啦,我的锁现在没被人持有。

简单的说就是:wait是对象通知持有自己锁的线程释放我的锁,notify()/notifyAll()就是对象通知刚刚被自己晾在一边的线程又可以来竞争我的锁了。

 

重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,

操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。前面我们在讲Java对象头的时候,

讲到了monitor这个对象,在hotspot虚拟机中,通过ObjectMonitor类来实现monitor。他的锁的获取过程的体现会简单很多。
技术图片

 

 

 

 1、wait()

public final void wait()  throws InterruptedException,IllegalMonitorStateException

该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。

进入wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。

如果调用wait()时,没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch结构。

2、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()方法执行后的情况不同。

3、notifyAll()

public final native void notifyAll() throws IllegalMonitorStateException

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

notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),

变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。

如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,

其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

 

以上是关于关于wait/notify的主要内容,如果未能解决你的问题,请参考以下文章

关于wait/notify

关于wait/notify

关于wait/notify

为什么wait()和notify()属于Object类

Java多线程_wait/notify/notifyAll方法

多线程的wait/notify的使用