Java生产者和消费者实现等待唤醒机制

 更新时间:2025年06月05日 09:54:21   作者:宝耶  
本文主要介绍了Java生产者和消费者实现等待唤醒机制,通过synchronized、wait()和notifyAll()实现线程间的安全协作,具有一定的参考价值,感兴趣的可以了解一下

本文将介绍如何使用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 等待唤醒机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mybatis错误之in查询 <foreach>循环问题

    mybatis错误之in查询 <foreach>循环问题

    这篇文章主要介绍了mybatis错误之in查询 <foreach>循环问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • MyBatis-Flex实现分页查询的示例代码

    MyBatis-Flex实现分页查询的示例代码

    在MyBatis-Flex中实现分页查询时,需要注意维护一个获取数据库总数的方法,详细介绍了UserService、UserServiceImpl类以及Mapper.xml配置,感兴趣的可以了解一下
    2024-10-10
  • 利用java、js或mysql计算高德地图中两坐标之间的距离

    利用java、js或mysql计算高德地图中两坐标之间的距离

    最近因为工作的需求,需要计算出高德地图中两个坐标的距离,通过查找相关资料发现了多种实现的方法,下面这篇文章主要给大家介绍了关于利用java、js或mysql计算高德地图中两坐标之间距离的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下。
    2017-10-10
  • java:java.lang.ExceptionInInitializerError报错解决过程

    java:java.lang.ExceptionInInitializerError报错解决过程

    这篇文章主要给大家介绍了关于java:java.lang.ExceptionInInitializerError报错的解决过程,java.lang.ExceptionInInitializerError 是一个异常,表示在初始化一个类的静态变量或静态块时发生了错误,需要的朋友可以参考下
    2023-10-10
  • Java编译和解释执行对比及原理解析

    Java编译和解释执行对比及原理解析

    这篇文章主要介绍了Java编译和解释执行对比及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • 详解如何使用SpringBoot封装Excel生成器

    详解如何使用SpringBoot封装Excel生成器

    在软件开发过程中,经常需要生成Excel文件来导出数据或者生成报表,为了简化开发流程和提高代码的可维护性,我们可以使用Spring Boot封装Excel生成器,本文将介绍如何使用Spring Boot封装Excel生成器,并提供一些示例代码来说明其用法和功能
    2023-06-06
  • Java简单使用redis-zset实现排行榜

    Java简单使用redis-zset实现排行榜

    这篇文章主要介绍了Java简单使用redis-zset实现排行榜,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • 在Java内存模型中测试并发程序代码

    在Java内存模型中测试并发程序代码

    这篇文章主要介绍了在Java内存模型中测试并发程序代码,辅以文中所提到的JavaScript库JCStress进行,需要的朋友可以参考下
    2015-07-07
  • 逆序对问题(Java实现)归并详解

    逆序对问题(Java实现)归并详解

    文章介绍了计算给定正整数序列中逆序对数量的方法,特别是使用归并排序进行拆分和合并时统计逆序对,通过递归拆分序列,并在合并过程中统计逆序对数量,从而得到整个序列的逆序对总数,此方法能有效处理包含重复数字的序列
    2026-05-05
  • Spring之什么是ObjectFactory?什么是ObjectProvider?

    Spring之什么是ObjectFactory?什么是ObjectProvider?

    这篇文章主要介绍了Spring之什么是ObjectFactory?什么是ObjectProvider?具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-01-01

最新评论