在Java里,Object.wait()/Object.wait(long)以及Object.notify()/Object.notifyAll()用于实现等待和通知。
wait()的作用是使其执行线程被暂停(对应进入生命周期WAITING),用来实现等待,称为等待线程。
notify()的作用是唤醒一个被暂停的线程,称为唤醒线程。
实现等待伪代码实现:
synchronized (someObject){
while(!someCondition){//保护条件不成立
//调用Object.wait()暂停当前线程
someObject.wait();
}
//执行到这说明保护条件已经满足
doAction();
}
保护条件是一个包含共享变量的Boolean值,这些共享便利被其他线程(通知线程)更新之后使得保护条件成立,然后通知线程唤醒等待线程。由于一个线程只有在持有对象内部锁的情况下才能够调用wait(),因此Object.wait()的调用总是放在相应对象引导的临界区中。
为什么保护条件要放在while循环?
someObject.wait()是在临界区执行,那么wait()方法就会以原子操作的方式使当前线程暂停并释放当前线程持有的someObject的内部锁。当前线程对wait()的调用并没有返回(需要被唤醒之后再次申请someObject的内部锁,申请成功才能继续执行someObject.wait()的剩余指令,知道wait()方法返回)。另外该方法可能会被多个线程执行,那么就可能会存在多个等待线程,那么就有可能在通知线程更新保护条件成立并唤醒等待线程的时候,是在多个等待线程中随机算一个唤醒,这就产生虚假唤醒,这时候检测一下保护条件并不成立,被唤醒线程需要继续等待;此外还有可能在该等待线程被正确唤醒,在继续运行以及申请锁的过程中,共享变量又被另一个线程更新到不成立,那么在此判断共享变量,该唤醒线程依旧需要继续等待。
所以对保护条件的判断以及Object.wait()的调用应该都放在循环语句中,以确保目标动作只有在保护条件成立的情况下才能够执行
另外,等待线程对保护条件的判断以及目标动作的执行必须是原子操作,否则可能产生线程安全问题(目标动作执行前的那一刻其他线程更新了共享变量使得保护条件又不成立)。
实现唤醒伪代码实现:
synchronized (someObject){
//更新等待线程的保护条件
updateSharedState();
//唤醒等待线程
someObject.notify();
}
唤醒线程主要做了两件事:更新共享变量、唤醒等待线程。
且唤醒线程也只有在持有对象内部锁的情况下才能执行someObject.notify()方法,且对象内部锁只有临界区代码结束才释放,notify()方法尽量靠近临界区结束的地方(避免唤醒线程调用notify但是临界区还没执行完成,导致等待线程被唤醒但是申请锁失败)。
wait()/notify()内部实现
JVM会为每一个对象维护一个入口集(Entry Set)用于存储申请该对象内部锁的线程,此外JVM还会为每个对象维护一个被等待队列(Wait Set),用于存储该对象上的等待线程,状态是WAITING(Entry Set里面维护的是争抢锁失败的线程,状态是BLOCKED)。
Object.wait()将当前线程暂停并释放相应内部锁的同时会将当前线程的引用存入所属对象的Wait Set中。
Object.notify()会使Wait Set里面的任意一个线程唤醒,被唤醒的线程仍在Wait Set中知道他申请内部锁成功,持有相应内部锁的时候(Object.wait()尚未返回)wait()方法会将当前线程移出Wait Set,接着wait()方法调用结束并返回。
所以wait()方法有几个关键点:将当前线程加入Wait Set,暂停当前线程,释放锁以及被唤醒之后重新申请锁,从Wait Set移出。
public void wait(){
if(!Thread.holdsLock(this)){
throws new IllegalMonitorStateException();
}
if(当前线程引用不在Wait Set){
//添加当前线程到Wait Set
addToWaitSet(Thread.currentThread());
}
atomic(//原子操作开始
//释放当前对象内部锁
releaseLock(this);
//暂停当前线程
LockSupport.park(this);
)
//被唤醒之后。。。wait方法继续下面的代码
//再次申请当前对象内部锁
acquierLock(this);
//将当前对象从当前对象的Wait Set移出
removeFromWaitSet();
}
总的来说,wait/notify太过于底层,并且存在过早唤醒的问题以及void Object.wait(long)无法区分其返回是由于超时还是被通知线程唤醒的问题。
Condition接口则可作为替代wait/notify,实现等待/通知功能。
网友评论