wait/notify之虚假唤醒(二)
一:sleep和wait的区别
讲这个虚假唤醒之前先来详细说明sleep和wait之间的区别
1:sleep是Thread线程类中的方法,为wait是Object类中的方法
2:sleep方法不会释放锁,但是wait会释放锁
3:sleep可以单独放在很多地方使用,wait通常和notify(await/singnal)一起使用
4:sleep不需要被唤醒(休眠之后自己退出阻塞),而wait需要被唤醒,并且之后要重新加入队列,进行竞争锁
二:简单的例子演示一下问题
现在假如如果要交替顺序加减1:
标准操作线程的步骤:
1:先创建一个公共的资源类
2:线程操作资源类(避免在资源类中进行线程的操作,两者分开,可以避免耦合)
公共资源类:
package duoxiancheng.printabc;
/**
* 测试虚假唤醒
* @author Huxudong
* @date 2020-06-18 13:52:27
**/
public class ShareData {
private int number = 0;
public synchronized void increment() throws InterruptedException {
/** 判断 */
while(number != 0) {
/** 条件不满足,就进入wait等待 */
this.wait();
}
/** 干活 */
number++;
System.out.println(Thread.currentThread().getName()+" "+number);
/** 唤醒 */
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
/** 判断 */
while(number == 0) {
/** 条件不满足,就wait等待 */
this.wait();
}
/** 条件满足就可以干活 */
number--;
System.out.println(Thread.currentThread().getName()+" "+number);
/** 唤醒 */
this.notifyAll();
}
}
package duoxiancheng.printabc;
/**
* 线程之间的通信
* 线程操作资源类
* @author Huxudong
* @date 2020-06-18 13:34:47
**/
public class Test {
public static void main(String[] args) {
/** 打印A-5次 B-10次 C-15次 */
// printABC();
/** 打印XYZ各10次 */
//printXYZ();
ShareData shareData = new ShareData();
for (int i = 0; i < 2; i++) {
new Thread(()->{
try {
shareData.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
},Integer.toString(i)).start();
new Thread(()->{
try {
shareData.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
},Integer.toString(i)).start();
}
}
private static void printABC() {
CommonResources commonResources = new CommonResources();
new Thread(()->{
commonResources.print5();
},"A").start();
new Thread(()->{
commonResources.print10();
},"B").start();
new Thread(()->{
commonResources.print15();
},"C").start();
}
private static void printXYZ() {
PrintResources resources = new PrintResources();
new Thread(()->{
resources.printX();
},"A").start();
new Thread(()->{
resources.printY();
},"B").start();
new Thread(()->{
resources.printZ();
},"C").start();
}
}
此时如果在资源类中,使用if语句判断,当线程数量少于3个(不包括3个),不影响结果。
但是稍微改动,线程数量大于3的时候,就会有错误的结果出现。
上一篇中已经说到,wait使用时,可能会发生虚假唤醒,所以判断一定要使用while,不能使用if。
这里个人对虚假唤醒的原因理解:因为wait的方法有三步
1:释放锁并阻塞
2:等待唤醒条件发生
3:获取通知后,竞争获取锁
当第二步有条件唤醒时候,在多线程环境下面可能有多个满足条件的线程被唤醒,但实际上条件并不满足,生产者生产出来的消费品已经被第一个线程消费了。如果是if判断,此时代码会依然走下去,这样就会出去问题了。
这就是我们使用while去做判断而不是使用if的原因:因为等待在条件变量上的线程被唤醒有可能不是因为条件满足而是由于虚假唤醒。所以,我们需要对条件变量的状态进行不断检查直到其满足条件。