生产者-消费者问题

使用synchronized实现生产消费

虚假等待

虚假唤醒意味着线程在没有满足 wait() 条件的情况下被唤醒。由于 wait() 可能会由于多种原因(如操作系统调度等)发生虚假唤醒,线程被唤醒后可能发现它原本等待的条件并未改变

示例分析:

scss 复制代码
public synchronized void produce() throws InterruptedException {
    if (queue.size() == 1) {
        wait();  // 如果缓冲区满了,生产者等待
    }
    queue.add(1);  // 生产产品
    notify();  // 唤醒消费者
}

public synchronized void consume() throws InterruptedException {
    if (queue.isEmpty()) {
        wait();  // 如果缓冲区空了,消费者等待
    }
    queue.remove(0);  // 消费产品
    notify();  // 唤醒生产者
}

这里使用了if来判断是否(满/空);

假设一下步骤发生:

1.生产者线程add(1),然后唤醒其他线程;

2.由于某些原因(比如操作系统调度、虚假唤醒等),消费线程 被唤醒,即使缓冲区仍然为空, 它依然开始执行。

3.消费线程 执行判断queue.isEmpty(),如果他被虚假唤醒,判断结果为true,然后线程会继续进入消费逻辑,尝试从一个空的缓冲区取数据。

反制生产者亦是。

因为 if只能判断一次,如果被虚假唤醒消费线程后,就失效了。 使用 while 循环代替 if 可以解决这个问题,因为 while 在每次被唤醒时重新检查条件 ,确保条件符合才继续执行

scss 复制代码
public synchronized void produce() throws InterruptedException {
    wgile (queue.size() == 1) {
        wait();  // 如果缓冲区满了,生产者等待
    }
    queue.add(1);  // 生产产品
    notify();  // 唤醒消费者
}

public synchronized void consume() throws InterruptedException {
    wgile (queue.isEmpty()) {
        wait();  // 如果缓冲区空了,消费者等待
    }
    queue.remove(0);  // 消费产品
    notify();  // 唤醒生产者
}

Lock版的生产者消费问题

synchronized的对比,三件套。

使用LockConditon实现的生产消费,下边这个代码本质和synchronized类似

csharp 复制代码
public class ProducerConsumer {


    public static void main(String[] args) {
        Task task = new Task();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                task.inSale();
            }
        }, "生产者1").start();


        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                task.inSale();
            }
        }, "生产者2").start();



        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                task.deSale();
            }
        }, "消费者1").start();


        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                task.deSale();
            }
        }, "消费者2").start();

    }

}

class Task {
    public Integer count = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void inSale() {
        lock.lock();
        try {
            while (count != 0) { 
                condition.await();
            }
            count++;
            System.out.println(Thread.currentThread().getName() + "生产了一个产品,目前产品数量为:" + count);
            condition.signalAll(); // 唤醒所有等待线程
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public void deSale() {
        lock.lock();
        try {
            while (count == 0) { 
                condition.await();
            }
            count--;
            System.out.println(Thread.currentThread().getName() + "消费了一个产品,目前产品数量为:" + count);
            condition.signalAll(); // 唤醒所有等待线程
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}

但是你会发现他没有按照一定顺序生产消费

Condition实现精准通知和唤醒线程

使用Condition可以精准唤醒你想要实现的线程 例如:A->C->B->D

csharp 复制代码
public class ProducerConsumer {


    public static void main(String[] args) {
        Task task = new Task();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                task.printA();
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                task.printB();
            }
        }, "B").start();
        
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                task.printC();
            }
        }, "C").start();
        
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                task.printD();
            }
        }, "D").start();
    }

}

class Task {
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    Condition condition4 = lock.newCondition();
    //1=》A 2=》B 3=》C 4=》D
    private int number = 1;

    public void printA() {
        lock.lock();
        try {
            while (number != 1) {
                condition1.await();
            }
            number = 3;
            System.out.println(Thread.currentThread().getName() + "生产了一个产品,唤醒C:" + number);
            condition3.signal(); // 唤醒C线程
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
    public void printB() {
        lock.lock();
        try {
            while (number != 2) {
                condition2.await();
            }
            number = 4;
            System.out.println(Thread.currentThread().getName() + "生产了一个产品,唤醒D:" + number);
            condition4.signal(); // 唤醒D线程
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {
            while (number != 3) {
                condition3.await();
            }
            number = 2;
            System.out.println(Thread.currentThread().getName() + "生产了一个产品,唤醒B:" + number);
            condition2.signal(); // 唤醒A线程
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
    public void printD() {
        lock.lock();
        try {
            while (number != 4) {
                condition4.await();
            }
            number = 1;
            System.out.println(Thread.currentThread().getName() + "生产了一个产品,唤醒A:" + number);
            condition1.signal(); // 唤醒B线程
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}
相关推荐
QZQ541882 小时前
go中channel通信的底层实现
后端
方圆想当图灵3 小时前
深入浅出 gRPC
java·后端·github
好哇塞3 小时前
Java 团队代码规范落地:Checkstyle/PMD/SpotBugs 开发环境集成指南
后端
王嘉俊9253 小时前
Flask 入门:轻量级 Python Web 框架的快速上手
开发语言·前端·后端·python·flask·入门
yeyong3 小时前
没有arm64 cpu, 在本地amd64环境上如何制作arm64架构下可用的镜像
后端
做运维的阿瑞4 小时前
Python 面向对象编程深度指南
开发语言·数据结构·后端·python
RoyLin4 小时前
V8引擎与VM模块
前端·后端·node.js
yinke小琪4 小时前
凌晨2点,我删光了所有“精通多线程”的代码
java·后端·面试
Cherry Zack4 小时前
Django 视图与路由基础:从URL映射到视图函数
后端·python·django