生产者-消费者问题

使用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();
        }
    }
}
相关推荐
Java小混子14 分钟前
golang项目CRUD示例
开发语言·后端·golang
想搞艺术的程序员17 分钟前
Go 优雅关闭实践指南:从原理到框架落地
开发语言·后端·golang
JohnYan21 分钟前
Bun技术评估 - 29 Docker集成
javascript·后端·docker
华仔啊33 分钟前
MyBatis-Plus 让你开发效率翻倍!新手也能5分钟上手!
java·后端·mybatis
绝无仅有43 分钟前
某东互联网大厂的Redis面试知识点分析
后端·面试·架构
乌暮1 小时前
JavaEE入门--计算机是怎么工作的
java·后端·java-ee
前端世界1 小时前
ASP.NET 实战:用 CSS 选择器打造一个可搜索、响应式的书籍管理系统
css·后端·asp.net
Java水解2 小时前
MySQL 正则表达式:REGEXP 和 RLIKE 操作符详解
后端·mysql
金銀銅鐵2 小时前
[Java] 用 Swing 生成一个最大公约数计算器(展示计算过程)
java·后端·数学
知其然亦知其所以然2 小时前
面试官笑了:我用这套方案搞定了“2000w vs 20w”的Redis难题!
redis·后端·面试