wait、notify、notifyall都是Object的native方法,具体的实现可通过jvm源码进行分析。 在单线程编程中,在执行某个具体动作之前如果需要满足某个条件时,我们可以将条件放在if()条件中,当条件满足时具体动作才能执行,在多线程场景下,我们可以通过wait、notify方法实现。

------------------------------
if(前提条件不成立){
	 //阻塞;
	 lock.wait();
}
doAction();
-------------------------------
//唤醒等待队列
前提条件=true;
lock.notify();

wait()、notify()方法调用的前提是必须持有锁,因为wait()的调用需要释放锁,我们知道jvm虚拟机会为每一个对象维护一个入口集(Entry Set)用于存储申请该对象内部锁的线程,还会为每个对象维护一个等待集(Wait Set)用于储存该对象锁的等待线程,wait()将当前线程暂停并释放相应内部锁的同时会将当前线线程的引用存入该方法所属对象的等待集中,执行notify()会唤醒持有同一锁对象的等待集中随机一个等待线程,但是需要同步块执行完后才会释放锁,所以wait()、notify()、notifyall()的调用必须在获得锁的代码块内部,比如sychnorized修饰的代码块或者Lock.lock的代码块内部。

wait方法的内部实现包括如下所示步骤:

public void wait() {
	// 执行线程必须持有当前对象对应的内部锁
	if (!Thread. holdsLock (this)) {
		throws new IllegalMonitorStateException() ;
	if (当前线程引用不在等待集中){
		//将当前线程加入当前对象的等待集中
		addToWaitSet (Thread.currentThread ());
	}
	atomic {//原子操作开始
		//释放当前对象的内部锁
		releaseLock(this );
		 //暂停当前线程
		block (Thread.currentThread ()); 
	}//原子操作结束

	//再次申请当前对象的内部锁
	acquireLock ( this );
	//将当前线程从当前对象的等待集中移除
	removeFromWaitSet (Thread.currentThread ());
	return ; //返回

调用wait方法,首先判断是否持有当前锁,否则抛出异常; 判断当前线程引用不在对象锁的等待集中,则将当前线程存入等待队列中,然后释放锁,并且暂停线程,这两步是一个原子操作,一旦notify将其唤醒,就开始执行接下来的步骤,先申请当前锁,如果获取到锁,从等待队列中移除。

wait/notify会带来哪些问题?

1.过早唤醒问题,等待线程W和通知线程N都为同步对象someObject锁线程,如果通知线程N3更新好了共享变量,调用notifyall通知唤醒所有someObject等待集的等待线程,但是等待线程W1、W2发现其保护条件并没有成立,这就使得该线程被唤醒之后仍然需要继续等待,这种等待线程在其所需的保护条件并未成立的情况下被唤醒的现象就被称为过早唤醒造成资源浪费。 2.信号丢失问题, 3.欺骗性唤醒问题 4.上下文切换问题 首先,等待线程执行Object.wait ()至少会导致该线程对相应对象内部锁的两 次申请与释放。通知线程在执行Object.notify() / notifyAll()时需要持有相应对象的 内部锁,因此Object.notify()/ notifyAll()调用会导致一次锁的申请。而锁的申请与 释放可能导致上下文切换。 其次,等待线程从被暂停到唤醒这个过程本身就会导致上下文切换。 再次,被唤醒的等待线程在继续运行时需要再次申请相应对象的内部锁,此 时等待线程可能需要和相应对象的人口集中的其他线程以及其他新来的活跃线 程(即申请相应的内部锁且处于RUNNABLE 状态的钱程)争用相应的内部锁, 而这又可能导致上下文切换。 最后,过早唤醒问题也会导致额外的上下文切换,这是因为被过早l唤醒的线 程仍然需要继续等待,即再次经历被暂停和唤醒的过程。