线程产生的虚假唤醒问题 原因和解决

简介: 多个线程并发争抢一个资源会产生线程虚假唤醒问题

传统模式下的生产者消费者

1、synchronized控制的

class Data{

    int number = 0;

    AtomicInteger atomicInteger = new AtomicInteger(0);

    public void increment(){
        synchronized (this){
            // 不等于0进行,等待消费者消费
            while (number != 0){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 生产
            number++;
            System.out.println(Thread.currentThread().getName()+" 生产了一个产品: " +number);

            // 通知消费者消费
            this.notify();
        }
    }


    public void decrement(){
        synchronized (this){
            // 等待生产者生产
            while (number == 0){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 消费
            number--;
            System.out.println(Thread.currentThread().getName()+" 消费了一个产品: " +number);

            // 通知生产者生产
            this.notify();
        }
    }

}

public static void main(String[] args) {

        // 任务: 生产一个消费一个
        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                data.increment();
            }
        },"producer").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                data.decrement();
            }
        },"consumer").start();


    }

2、lock(ReentrantLock)

class Data{

    int number = 0;

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void increment(){
         lock.lock();
         try {
             // 不等于0进行,等待消费者消费
             while (number != 0){
                 try {
                     condition.await();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }

             // 生产
             number++;
             System.out.println(Thread.currentThread().getName()+" 生产了一个产品: " +number);

             // 通知消费者消费
             condition.signal();
         }finally {
             lock.unlock();
         }

    }


    public void decrement(){
        lock.lock();
        try {
            // 等待生产者生产
            while (number == 0){
                try {
                   condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 消费
            number--;
            System.out.println(Thread.currentThread().getName()+" 消费了一个产品: " +number);

            // 通知生产者生产
            condition.signal();
        }finally {
            lock.unlock();
        }
    }

}

public static void main(String[] args) {

        // 任务: 生产一个消费一个
        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                data.increment();
            }
        },"producer").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                data.decrement();
            }
        },"consumer").start();


    }

两者运行结果:
在这里插入图片描述

虚假唤醒问题

存在多个线程并发争抢一个资源。以生产者消费者为例:

我们任务要求,只能生产一个产品消费一个产品。两个生产者生产,两个消费者消费。

当生产者生产完一个产品时,要唤醒等待的线程(notify是随机唤醒)。注意此时有两个消费者线程,一个生产者线程等待。如果cpu的调度权被等待的生产者获取到了,此时生产者在 wait()方法处 会直接往下执行,实际上就生产了两个产品。同理消费者也可能同时消费两个产品

根源在于:换性的线程是直接往下执行的并没有判断是否满足对应条件
在这里插入图片描述
产生虚假唤醒的源码

class Data{

    int number = 0;

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void increment(){
         lock.lock();
         try {
             // 不等于0进行,等待消费者消费
             if (number != 0){
                 try {
                     condition.await();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }

             // 生产
             number++;
             System.out.println(Thread.currentThread().getName()+" 生产了一个产品: " +number);

             // 通知消费者消费
             condition.signal();
         }finally {
             lock.unlock();
         }

    }


    public void decrement(){
        lock.lock();
        try {
            // 等待生产者生产
            if (number == 0){
                try {
                   condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 消费
            number--;
            System.out.println(Thread.currentThread().getName()+" 消费了一个产品: " +number);

            // 通知生产者生产
            condition.signal();
        }finally {
            lock.unlock();
        }
    }

}

public static void main(String[] args) {

        // 任务: 生产一个消费一个
        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                data.increment();
            }
        },"producer").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                data.decrement();
            }
        },"consumer").start();


        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                data.increment();
            }
        },"producer1").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                data.decrement();
            }
        },"consumer2").start();


    }

可能的结果
在这里插入图片描述

解决:
if该while即可,唤醒的同时,进行再次判断
在这里插入图片描述

相关文章
|
4月前
|
缓存 Java 容器
多线程环境中的虚假共享是什么?
【8月更文挑战第21天】
39 0
|
6月前
|
Java
使用notifyAll唤醒所有等待线程的方法与比较
使用notifyAll唤醒所有等待线程的方法与比较
|
6月前
|
Oracle Java 关系型数据库
面试知识点:notify是随机唤醒线程吗(唤醒线程顺序)?
面试知识点:notify是随机唤醒线程吗(唤醒线程顺序)?
181 0
|
Java
线程等待唤醒(等待通知)机制
线程等待唤醒(等待通知)机制
44 0
线程发生阻塞,怎么唤醒线程?
线程发生阻塞,怎么唤醒线程?
233 0
|
算法 Java 调度
线程的挂起和唤醒
线程的挂起和唤醒
|
安全 Java C++
JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)
JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)
223 1
JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)
JUC学习(三):synchronized和Lock实现线程间通信(包含虚假唤醒的讲解)
JUC学习(三):synchronized和Lock实现线程间通信(包含虚假唤醒的讲解)
JUC学习(三):synchronized和Lock实现线程间通信(包含虚假唤醒的讲解)
|
Java 程序员 API
JUC - 线程中断与线程等待、唤醒(LockSupport)
JUC - 线程中断与线程等待、唤醒(LockSupport)
JUC - 线程中断与线程等待、唤醒(LockSupport)
|
调度
【多线程:wait/notify详解】原理及错误用法(虚假唤醒等)
【多线程:wait/notify详解】原理及错误用法(虚假唤醒等)
202 0