生产者-消费者问题

使用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的对比,三件套。

使用Lock与Conditon实现的生产消费,下边这个代码本质和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();
        }
    }
}
相关推荐
雪宫街道10 分钟前
SpringBoot - POI实现Excel 导入与导出
spring boot·后端·excel
打工仔折腾 AI33 分钟前
让时序预测真正参与决策:TimechoAI 工程化落地与管线设计实践
人工智能·后端·python·算法·机器学习·性能优化
CarIise42 分钟前
Spring MVC入门与执行流程
后端
雨中飘荡的记忆1 小时前
子代理和 Skills 怎么写?3 个实用例子
后端
IT_陈寒1 小时前
一文搞懂React的核心概念
前端·人工智能·后端
高频因子挖掘机1 小时前
多只股票的 K 线日期对不上怎么办?用 Pandas MultiIndex 处理时间序列对齐
后端·github·api
高频因子挖掘机1 小时前
行情 API 返回了数据,为什么量化策略仍然会出错?用分层设计隔离数据故障
后端·github·api
十年Java程序媛10 小时前
Java枚举Enum完整教程|替代静态常量,开发与面试要点
java·开发语言·spring boot·后端·面试
我是人✓10 小时前
SpringBoot静态资源映射揭秘
java·spring boot·后端
全栈弄潮儿10 小时前
Python实战第7期:字典与集合
后端·python·编程语言