生产者-消费者问题

使用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();
        }
    }
}
相关推荐
子兮曰5 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码5 天前
别再前后端各写一套表单校验了
java·后端
大勇前进6 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu6 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile6 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白806 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端
二月龙6 天前
PHP 接口返回统一响应封装,让前后端对接更省心
后端
盖伦发发6 天前
软件工程SOLID 五大设计原则
后端·软件工程