生产者和消费者造成wait()虚假唤醒问题

synchronized版

生产者和消费者问题

创建生产者和消费者,使用两个线程去操作同一个资源!

package demo2;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 使用多线程,操作同一个类
        Data data = new Data();
        // 线程A 负责增加
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data.increment();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();
        // 线程B 负责减少
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data.decrement();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}

/**
 * 数据操作类;<br />
 * 实现 num 数据的 +1 和 -1 操作
 */
class Data{
    
    
    private int num = 0;

    // 数据 +1 ,添加 synchronized 实现线程的加锁
    public synchronized void increment() throws InterruptedException {
    
    
        // 如果现在的num是0,则+1,否则等待其他线程的通知
        if(num != 0){
    
    
            // 等待
            this.wait();
        }
        // 否则 +1
        num ++ ;
        System.out.println(Thread.currentThread().getName()+" num=>"+num);
        // 通知其他线程(通知所有)
        this.notifyAll();
    }

    // 数据 -1 操作
    public synchronized void decrement() throws InterruptedException {
    
    
        // 如果现在的num是0,则+1,否则等待其他线程的通知
        if(num == 0){
    
    
            // 等待
            this.wait();
        }
        // 否则 +1
        num -- ;
        System.out.println(Thread.currentThread().getName()+" num=>"+num);
        // 通知其他线程(通知所有)
        this.notifyAll();
    }
}

当只有两个线程时,此时消费者消费生产者产出的数据时,会得到以下的日志打印信息:
在这里插入图片描述

如果此时出现的消费者线程不是两个呢?假设3个、4个?

多个消费者消费同一生产者数据问题

依旧是一个生产者负责数据的+1,-1操作;但此时的消费者则有超过2个!

package demo2;

public class Test2 {
    
    
    public static void main(String[] args) {
    
    
        // 使用多线程,操作同一个类
        Data2 data2 = new Data2();
        // 线程A 负责增加
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data2.increment();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();
        // 线程B 负责减少
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data2.decrement();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();

        // 线程C 负责增加
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data2.increment();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"C").start();
        // 线程D 负责减少
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data2.decrement();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

/**
 * 数据操作类;<br />
 * 实现 num 数据的 +1 和 -1 操作
 */
class Data2{
    
    
    private int num = 0;

    // 数据 +1 ,添加 synchronized 实现线程的加锁
    public synchronized void increment() throws InterruptedException {
    
    
        // 如果现在的num是0,则+1,否则等待其他线程的通知
        if(num != 0){
    
    
            // 等待
            this.wait();
        }
        // 否则 +1
        num ++ ;
        System.out.println(Thread.currentThread().getName()+" num=>"+num);
        // 通知其他线程(通知所有)
        this.notifyAll();
    }

    // 数据 -1 操作
    public synchronized void decrement() throws InterruptedException {
    
    
        // 如果现在的num是0,则+1,否则等待其他线程的通知
        if(num == 0){
    
    
            // 等待
            this.wait();
        }
        // 否则 +1
        num -- ;
        System.out.println(Thread.currentThread().getName()+" num=>"+num);
        // 通知其他线程(通知所有)
        this.notifyAll();
    }
}

多个线程去操作同一资源时,则此时运行代码(多次运行),会出现以下问题:
在这里插入图片描述

多个线程造成的问题分析

在调用 java.lang.Object 类的 wait() 时,几率出现虚假唤醒的现象!
在这里插入图片描述
按照官方文档给出的建议,此时则需要修改for为while。

public class Test3 {
    
    
    public static void main(String[] args) {
    
    
        // 使用多线程,操作同一个类
        Data3 data3 = new Data3();
        // 线程A 负责增加
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data3.increment();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();
        // 线程B 负责减少
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data3.decrement();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();

        // 线程C 负责增加
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data3.increment();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"C").start();
        // 线程D 负责减少
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data3.decrement();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
/**
 * 数据操作类;<br />
 * 实现 num 数据的 +1 和 -1 操作<br />
 *
 * 为了防止 java.lang.Object 中的 wait() 虚假唤醒,需要修改if 为 while。
 */
class Data3{
    
    
    private int num = 0;

    // 数据 +1 ,添加 synchronized 实现线程的加锁
    public synchronized void increment() throws InterruptedException {
    
    
        // 如果现在的num是0,则+1,否则等待其他线程的通知
        // 防止虚假等待,修改if为while
        while(num != 0){
    
    
            // 等待
            this.wait();
        }
        // 否则 +1
        num ++ ;
        System.out.println(Thread.currentThread().getName()+" num=>"+num);
        // 通知其他线程(通知所有)
        this.notifyAll();
    }

    // 数据 -1 操作
    public synchronized void decrement() throws InterruptedException {
    
    
        // 如果现在的num是0,则+1,否则等待其他线程的通知
        // 防止虚假等待,修改if为while
        while(num == 0){
    
    
            // 等待
            this.wait();
        }
        // 否则 +1
        num -- ;
        System.out.println(Thread.currentThread().getName()+" num=>"+num);
        // 通知其他线程(通知所有)
        this.notifyAll();
    }
}

问题根源

  • wait() 存在虚假唤醒的情况。
    在这里插入图片描述

  • if 判断只会判断一次,while中条件为true时为持续判断!
    当某个线程执行了this.notifyAll(),此时如果是if(xxx)(if只会判断一次)则取消了其中的this.wait(),执行了下列的流程;但是如果此时是while(xxx),则会继续进行判断,当时别到判断条件不符合时,才会继续执行下面的代码。

JUC实现

上面的代码中,为了解决 java.lang.Object.wait()造成的虚假唤醒问题,将if 一次判断更改为while 持续判断

使用JUC下的Condition

java.util.concurrent包下有更好的解决加锁等待通知释放锁的操作,看下列栗子:

package demo2_1;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 操作同一个资源
        Data data = new Data();
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data.increment();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data.decrement();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data.increment();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data.decrement();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

/**
 * 使用 Condition 实现类似Object的wait()和notify()方式,解决wait()虚假唤醒问题
 */
class Data{
    
    
    private int num = 0;

    // 获取一个锁
    Lock lock = new ReentrantLock();
    // 获取可操作的 Condition 类
    Condition condition = lock.newCondition();

    // 数据 +1
    public void increment() throws InterruptedException {
    
    
        // 加锁 如果是使用 tryLock(),需要判断是否成功获取到了锁
        lock.lock();
        try {
    
    
            // 判断当前的数据是否是0,如果是0则+1,不是则等待
            while (num != 0){
    
    
                // 等待唤醒
                condition.await();
            }
            // 否则 +1
            num ++;
            System.out.println(Thread.currentThread().getName()+" num = "+num);
            // 通知其他所有线程 其他线程很多,直接使用 signalAll
            condition.signalAll();

        }finally {
    
    
            // 前面加了锁,此时则需要释放锁,为了保证异常也能释放,需要放在finally中
            lock.unlock();
        }

    }

    // 数据 -1
    public void decrement() throws InterruptedException {
    
    
        // 加锁 如果是使用 tryLock(),需要判断是否成功获取到了锁
        lock.lock();
        try {
    
    
            // 判断当前的数据是否是0,如果是0则+1,不是则等待
            while (num == 0){
    
    
                // 等待唤醒
                condition.await();
            }
            // 否则 +1
            num --;
            System.out.println(Thread.currentThread().getName()+" num = "+num);
            // 通知其他所有线程 其他线程很多,直接使用 signalAll
            condition.signalAll();

        }finally {
    
    
            // 前面加了锁,此时则需要释放锁,为了保证异常也能释放,需要放在finally中
            lock.unlock();
        }

    }
}

运行代码,控制台日志打印信息如下所示:
在这里插入图片描述

使用Condition实现精准唤醒

从上面的日志中,可以发现:

使用 Condition可以实现类似java.lang.Object中的wait()notifyAll()的操作,使用java.util.concurrent中的locks.Lock可以实现类似synchronized加锁释放锁的要求。

但,执行的线程很乱,如何达到精准唤醒的操作?

实现:
A执行->唤醒B
B执行->唤醒C
C执行->唤醒D
D执行->唤醒A

实现方式:

采取定义不同的 Condition 监视器,执行完指定的逻辑方法后,唤醒对应的那个监视器。

代码实现:

package demo2_1;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Condition 精准唤醒的实现方式;<br />
 * A执行完成-》唤醒B,B执行完-》唤醒C,C执行完—》唤醒A
 */
public class Test2 {
    
    
    public static void main(String[] args) {
    
    
        // 操作同一个资源
        Data2 data2 = new Data2();
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data2.printA();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data2.printB();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    data2.printC();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"C").start();

    }
}

/**
 * 使用 Condition 实现类似Object的wait()和notify()方式,解决wait()虚假唤醒问题
 */
class Data2{
    
    
    private int num = 1;

    // 获取一个锁
    Lock lock = new ReentrantLock();
    // 获取可操作的 Condition 类,实现监视器(由于是三个方法,所以创建3个不同的监视器)
    // 不同的监视器对应不同的线程,假设 conditionA 对应 线程A
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();

    public void printA() throws InterruptedException {
    
    
        // 加锁
        lock.lock();
        try{
    
    
            // 判断当前 num属性是否为1,不是则等待
            while (num != 1){
    
    
            	// A 自己的监视器等待
                conditionA.await();
            }
            // 否则则将num改为2
            num = 2;
            System.out.println(Thread.currentThread().getName()+"执行成功,唤醒B");
            // 唤醒 B 监视器
            conditionB.signal();
        }finally {
    
    
            // 释放锁
            lock.unlock();
        }
    }

    public void printB() throws InterruptedException {
    
    
        // 加锁
        lock.lock();
        try{
    
    
            // 判断当前 num属性是否为2,不是则等待
            while (num != 2){
    
    
                conditionB.await();
            }
            // 否则则将num改为3
            num = 3;
            System.out.println(Thread.currentThread().getName()+"执行成功,唤醒C");
            // 唤醒 C
            conditionC.signal();
        }finally {
    
    
            // 释放锁
            lock.unlock();
        }
    }

    public void printC() throws InterruptedException {
    
    
        // 加锁
        lock.lock();
        try{
    
    
            // 判断当前 num属性是否为3,不是则等待
            while (num != 3){
    
    
                conditionC.await();
            }
            // 否则则将num改为2
            num = 1;
            System.out.println(Thread.currentThread().getName()+"执行成功,唤醒A");
            // 唤醒 A
            conditionA.signal();
        }finally {
    
    
            // 释放锁
            lock.unlock();
        }
    }
}

执行后的结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_38322527/article/details/114639664