使用场景
在单线程下,要实现程序执行某操作必须满足某一条件可以将这个操作放在一格if的语句体中。在多线程下,一个线程执行的某个操作必须满足某个条件,且这个条件不满足只是暂时的,这个条件只是需要另一个线程来改变。在这种情况下,我们可以在条件不满足的时候将线程暂停进入WAIT,当条件满足的时候,再去将线程唤醒。
使用wait()和notify()实现上述场景
someObject.wait()/someObject.wait(long)
每一个对象都有此方法,对象调用此方法会使得对象所在的线程暂停进入WAIT状态(称此线程为该对象上的等待线程),直到由于条件满足或者超时(wait(long))才被唤醒。但是如果像如下直接调用此方法则会抛出异常
由于一个对象的方法可以被多个线程调用,所以一个对象可以有多个等待线程
someObject.wait()
Exception in thread "main" java.lang.IllegalMonitorStateException
下面来说一说调用此方法需要满足的条件
-
1,线程必须持有该对象的内部锁
-
2,保护条件:共享变量组成的表达式
-
3,执行动作
synchronized (this){ while(!保护条件){ someObject.wait(); } //执行动作 doAction(); }
前面提到过,在单线程下,我们只需要使用一个 if就可以实现需求。那么为什么在多线程下要加如此多的限制呢?
加锁是为了保障整个操作是原子性的不会出现竞态等线程安全问题。试想没有锁的保护,当一个线程开始因为保护条件不满足而wait,待条件满足后通知线程将他唤醒。这时在他被唤醒到执行doAction的过程中,共享变量又被改变而导致在条件不满足的情况下而执行了doAction(),其实使用while语句体也是为了不出现这种情况,在被唤醒后再次判断。
wait实现:
相当于如下伪代码
public void wait(){
if(!Thread.holdsLock(this)){ //检查当前线程是否拥有对象锁
throw new IllegalMonitorStateException();
}
if(!waitSet){ //如果不在等待集合中,加入到等待集合中
addToWaitSet(Thread.currentThread());
}
atomic{//原子操作
releaseLock(this); //释放锁
block(Thread.currentThread()); //暂停当前线程
}
acquireLock(this); //再次申请锁
removeFromWaitSet(Thread.currentThread()); //申请成功后,将线程从该对象的等待集合中移除
return;
}
java为每一个对象都维护着一个 wait set,用来存放调用wait而暂停的线程的引用。如图所示,当线程调用 wait会进入到等待集中,然后释放锁,进入 wait状态。但次数对 wait方法的调用并没有返回。当条件满足,当前线程必须再次申请锁,申请成功后将线程从wait set中移除,返回。对wait方法的调用结束。
someObject.notify()/someObject.notifyAll()
对象调用notify会唤醒相应对象等待集中任一线程。notifyAll会唤醒该对象等待集中所有的线程。调用该对象方法的线程称为通知线程。同样调用该方法必须持有该对象的内部锁,正是由于调用该方法需要对象内部锁,所以执行wait方法的线程必须在暂停线程后需要释放锁。否则通知线程无法获得锁,通知等待线程,等待线程由于没有通知线程会发生活性故障:死锁。
synchronized (this){
updateStatus();
someObject.notify();
}