Java生产者和消费者实现等待唤醒机制
作者:宝耶
本文将介绍如何使用Java多线程实现经典的生产者-消费者模型,通过synchronized、wait()和notifyAll()实现线程间的安全协作。
模型概述
生产者-消费者模型是多线程编程中的经典案例,主要解决以下问题:
- 生产者线程负责生产数据/资源
 - 消费者线程负责消费数据/资源
 - 生产者和消费者通过共享缓冲区进行通信
 - 需要解决线程同步和资源竞争问题
 
完整代码实现
1. Desk.java(共享数据类)
/**
 * 共享数据类
 * - foodflag: 食物状态标记(0=无食物,1=有食物)
 * - count: 剩余食物数量
 * - lock: 同步锁对象
 */
public class Desk {
    public static int foodflag = 0;
    public static int count = 10;
    public static final Object lock = new Object();
}2. Cook.java(生产者线程)
/**
 * 生产者线程 - 厨师
 */
public class Cook extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                // 如果食物已全部生产完成,则退出
                if (Desk.count == 0) {
                    break;
                }
                
                // 如果还有食物未被消费,则等待
                if (Desk.foodflag == 1) {
                    try {
                        Desk.lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    // 生产食物
                    System.out.println(Thread.currentThread().getName() + " 做了一碗面");
                    Desk.foodflag = 1;
                    // 通知消费者可以消费了
                    Desk.lock.notifyAll();
                }
            }
        }
        System.out.println(Thread.currentThread().getName() + " 停止生产");
    }
}3. Foodie.java(消费者线程)
/**
 * 消费者线程 - 吃货
 */
public class Foodie extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                // 如果食物已全部消费完,则退出
                if (Desk.count == 0) {
                    Desk.lock.notifyAll();  // 确保生产者线程能退出
                    break;
                }
                
                // 如果没有食物,则等待
                if (Desk.foodflag == 0) {
                    try {
                        Desk.lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    // 消费食物
                    Desk.count--;
                    System.out.println(Thread.currentThread().getName() + " 吃了一碗,还剩 " + Desk.count + " 碗");
                    Desk.foodflag = 0;
                    // 通知生产者可以生产了
                    Desk.lock.notifyAll();
                }
            }
        }
        System.out.println(Thread.currentThread().getName() + " 停止消费");
    }
}4. Main.java(测试类)
/**
 * 测试类
 */
public class Main {
    public static void main(String[] args) {
        // 创建并启动生产者线程
        Cook cook = new Cook();
        cook.setName("厨师");
        
        // 创建并启动消费者线程
        Foodie foodie = new Foodie();
        foodie.setName("吃货");
        
        cook.start();
        foodie.start();
    }
}关键点解析
1. 同步机制
我们使用synchronized关键字实现对共享资源的互斥访问:
synchronized (Desk.lock) {
    // 临界区代码
}2. 线程通信
通过wait()和notifyAll()实现线程间协作:
wait():释放锁并进入等待状态notifyAll():唤醒所有等待该锁的线程
3. 生产消费逻辑
生产者检查
foodflag:为0时生产食物并设置
foodflag=1为1时等待消费者消费
消费者检查
foodflag:为1时消费食物并设置
foodflag=0为0时等待生产者生产
执行结果示例
厨师 做了一碗面
吃货 吃了一碗,还剩 9 碗
厨师 做了一碗面
吃货 吃了一碗,还剩 8 碗
...
吃货 吃了一碗,还剩 0 碗
厨师 停止生产
吃货 停止消费
常见问题解答
Q1: 为什么使用notifyAll()而不是notify()?
notifyAll()会唤醒所有等待线程,让它们公平竞争锁,避免某些线程长期得不到执行的情况。而notify()只随机唤醒一个线程,可能导致线程饥饿。
Q2: 为什么要在finally块外调用notifyAll()?
因为notifyAll()必须在持有锁的情况下调用,而synchronized块结束时锁会自动释放,所以不需要在finally中调用。
Q3: 如何避免死锁?
确保每个
wait()都有对应的notifyAll()设置合理的退出条件(如
count == 0)避免嵌套锁
总结
通过这个案例,我们学习了:
如何使用
synchronized实现线程同步如何使用
wait()/notifyAll()实现线程通信生产者-消费者模型的经典实现
多线程编程中的常见问题及解决方案
这个模型可以应用于许多实际场景,如消息队列、任务调度等系统设计中。
到此这篇关于Java生产者和消费者实现等待唤醒机制的文章就介绍到这了,更多相关Java 等待唤醒机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
