java 虚假唤醒(SpuriousWakeups)

public class SpuriousWakeups {

private Object object = new Object();

public int count = 0;

public void get(int cnt) {
synchronized (object) {
if (count <= 0) {
try {
object.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
count = count - cnt;
System.out.println(Thread.currentThread() + ": final " + count);
}
}

public void put(int cnt) {
synchronized (object) {
count = count + cnt;
object.notify();
}
}


public static void main(String[] args) throws InterruptedException {

SpuriousWakeups spuriousWakeups = new SpuriousWakeups();

Thread put1 = new Thread(new Runnable() {
@Override public void run() {
spuriousWakeups.put(1);
}
});

Thread get1 = new Thread(new Runnable() {
@Override public void run() {
spuriousWakeups.get(1);
}
});

Thread get2 = new Thread(new Runnable() {
@Override public void run() {
spuriousWakeups.get(1);
}
});
//get2.setPriority(9);

// get1先获取, 让object.wait 释放object的monitor
get1.start();
Thread.sleep(100);
// 放入数据
put1.start();
get2.start();
}
}

执行的结果是:
1、
Thread[Thread-1,5,main]: final 0
get1获取monitor往下执行,结束后释放monitor;get2获得get1释放的monitor,其线程由BLOCKED状态转为WAITING状态
2、
Thread[Thread-2,5,main]: final 0
Thread[Thread-1,5,main]: final -1
get2抢先获得monitor执行完毕后,get1获得get2释放的monitor。至此线程全部执行完毕。

JDK推荐的写法:
public final void wait()
throws InterruptedException导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法。 换句话说,这个方法的行为就好像简单地执行呼叫wait(0) 。
当前的线程必须拥有该对象的显示器。 该线程释放此监视器的所有权,并等待另一个线程通知等待该对象监视器的线程通过调用notify方法或notifyAll方法notifyAll 。 然后线程等待,直到它可以重新获得监视器的所有权并恢复执行。

像在一个参数版本中,中断和虚假唤醒是可能的,并且该方法应该始终在循环中使用:

synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
} 该方法只能由作为该对象的监视器的所有者的线程调用。 有关线程可以成为监视器所有者的方式的说明,请参阅notify方法。


线程状态。 线程可以处于以下状态:
NEW
尚未启动的线程处于此状态。
RUNNABLE
在Java虚拟机中执行的线程处于此状态。
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED
已退出的线程处于此状态。

总结:
虚假唤醒原因:wait被notify后,线程由WAITING变成BLOCKED状态,来竞争monitor,但是另外一个线程BLOCKED也会来竞争monitor,是没法控制到底是谁先拿到monitor的。
如果不是wait的线程先拿到monitor,那当wait的线程拿到monitor的时候,共享的值已经改变了。







posted @ 2019-06-14 16:44  喝花茶  阅读(1324)  评论(1编辑  收藏  举报