线程的虚假唤醒情况

​ 多线程并发操作一直都是学习和工作过程中的难点,一般而言,在多个线程共享资源时,我们通常会使用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() + "\t" + number);
        //3通知
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
    
    
        // 1判断
        if (number == 0) {
    
    
            this.wait();
        }
        // 2干活
        --number;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3通知
        this.notifyAll();
    }
}

多次测试结果如下:

image-20201004193620658

在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();
    }
}

多次测试,出现了以下结果:

image-20201004193942276

我们发现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() + "\t" + number);
        //3通知
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
    
    
        // 1判断
        while (number == 0) {
    
    
            this.wait();
        }
        // 2干活
        --number;
        System.out.println(Thread.currentThread().getName() + "\t" + 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() + "\t" + number);
        //3通知
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        // 1判断
        if (number == 0) {
            this.wait();
        }
        // 2干活
        --number;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3通知
        this.notifyAll();
    }
}

控制台打印结果如下:

image-20201004203719676

将if改为while条件后述情况就不会存在,由于是while()循环,所有被等待的线程在获取到cpu执行权利后一定会进行条件判断,不会出现两个生产者交替获得CPU执行权将number+1的情况(也不会出现两个消费者交替获得cpu执行权的情况)

如图:

image-20201004204345780

猜你喜欢

转载自blog.csdn.net/qq_44134480/article/details/108922457