多线程并发操作一直都是学习和工作过程中的难点,一般而言,在多个线程共享资源时,我们通常会使用synchronized代码块的同步,并通过wait()、notify()和notifyAll()来唤醒或者等待线程(这三个方法必须使用在同步代码块或同步方法中,被同步监视器调用,否则会抛出异常)。
还是通过经典的生产者和消费者案例引出虚假唤醒的问题
public class SpuriousWakeupDemo {
public static void main(String[] args) {
ShareData resources = new ShareData();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
resources.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "producer").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
resources.decrement();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "consumer").start();
}
}
/**
* 资源类
*/
class ShareData{
private int number = 0;//初始值为零的一个变量
public synchronized void increment() throws InterruptedException {
//1判断
if (number != 0) {
this.wait();
}
//2干活
++number;
System.out.println(Thread.currentThread().getName() + " " + number);
//3通知
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 1判断
if (number == 0) {
this.wait();
}
// 2干活
--number;
System.out.println(Thread.currentThread().getName() + " " + number);
// 3通知
this.notifyAll();
}
}
多次测试结果如下:
在main方法中通过匿名内部类的方式创建了两个线程,一个作为生产者,一个作为消费者。上面这个代码正常运行,也并没有出现什么安全问题。但是我们却忽略了一个重要的点!!过我们们改动代码,在main方法中多加如两个生产者和消费者如下:
public class SpuriousWakeupDemo {
public static void main(String[] args) {
ShareData resources = new ShareData();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
resources.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "producer1").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
resources.decrement();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "consumer1").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
resources.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "producer2").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
resources.decrement();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "consumer2").start();
}
}
多次测试,出现了以下结果:
我们发现ShareData类中的共享变量number即使在不等于0的情况下依然在自增,increment()方法明明已经通过了synchronized进行了加锁,并且在方法内部做了判断当number!=0就将线程等待,为什么还是会出现number>0的情况?
解决上述问题很简单,就是将if判断改为while判断,上述问题就再也没有出现。
class ShareData{
private int number = 0;//初始值为零的一个变量
public synchronized void increment() throws InterruptedException {
//1判断
while (number != 0) {
this.wait();
}
//2干活
++number;
System.out.println(Thread.currentThread().getName() + " " + number);
//3通知
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 1判断
while (number == 0) {
this.wait();
}
// 2干活
--number;
System.out.println(Thread.currentThread().getName() + " " + number);
// 3通知
this.notifyAll();
}
}
这个问题的关键就在于当线程被唤醒时,从哪里运行。当我们使用if作为条件判断时,分别在wait()方法前后加两条打印语句
class ShareData{
private int number = 0;//初始值为零的一个变量
public synchronized void increment() throws InterruptedException {
//1判断
if (number != 0) {
System.out.println("生产线程"+Thread.currentThread().getName()+"准备休眠");
this.wait();
System.out.println("生产线程"+Thread.currentThread().getName()+"休眠结束");
}
//2干活
++number;
System.out.println(Thread.currentThread().getName() + " " + number);
//3通知
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 1判断
if (number == 0) {
this.wait();
}
// 2干活
--number;
System.out.println(Thread.currentThread().getName() + " " + number);
// 3通知
this.notifyAll();
}
}
控制台打印结果如下:
将if改为while条件后述情况就不会存在,由于是while()循环,所有被等待的线程在获取到cpu执行权利后一定会进行条件判断,不会出现两个生产者交替获得CPU执行权将number+1的情况(也不会出现两个消费者交替获得cpu执行权的情况)
如图: