当前位置:首页 > 技术 > 正文内容

Java线程间协作:基于wait与notify的生产者-消费者模式实现

访客 技术 2026年6月5日 1

线程协作的必要性

在多线程编程中,多个线程往往需要协同完成某项任务。例如,一个线程负责生成数据(生产者),另一个线程负责处理这些数据(消费者)。若没有有效的通信机制,可能导致资源浪费或逻辑错误,比如消费者在无数据时持续尝试读取,或生产者在缓冲区已满时仍强行写入。 为解决这类问题,Java提供了线程间的等待/唤醒机制,使线程能够根据条件暂停执行,并在条件满足时被其他线程唤醒,从而实现有序协作。

核心方法概述

Java通过以下三个定义在Object类中的方法支持线程通信:
  • wait():调用该方法的线程会释放当前持有的对象锁,并进入等待状态,直到被其他线程唤醒。
  • notify():随机唤醒一个正在等待该对象锁的线程,使其从等待队列转入就绪状态。
  • notifyAll():唤醒所有等待该对象锁的线程,由系统调度器决定哪个线程获得锁并继续执行。

这些方法必须在synchronized块或方法中调用,且操作的对象必须是同一把锁,否则会抛出IllegalMonitorStateException异常。

为何定义在Object类?

由于Java中每个对象都可作为同步监视器(即锁),而等待和唤醒操作是针对"某个对象的锁"进行的,因此将waitnotify等方法定义在Object类中是最合理的做法——所有对象天然具备这一能力。

wait() 与 sleep() 的关键区别

超时自动恢复或中断
特性 wait() sleep()
所属类 Object Thread
是否释放锁
使用环境 必须在synchronized中使用 任意位置
唤醒方式 notify()/notifyAll()

实际案例:食物生产与消费模型

下面通过一个典型的生产者-消费者场景演示线程协作机制。

共享资源类

public class SharedResource {
    private boolean hasProduct = false; // 标记是否有产品可供消费

    public synchronized void produce() {
        while (hasProduct) {
            try {
                wait(); // 若已有产品,则生产者等待
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        for (int i = 1; i <= 5; i++) {
            System.out.println("【生产】第 " + i + " 个商品");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        hasProduct = true;
        notify(); // 通知消费者可以开始消费
    }

    public synchronized void consume() {
        while (!hasProduct) {
            try {
                wait(); // 若无产品,则消费者等待
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        for (int i = 1; i <= 5; i++) {
            System.out.println("【消费】第 " + i + " 个商品");
            try {
                Thread.sleep(400);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        hasProduct = false;
        notify(); // 通知生产者可以继续生产
    }
}

生产者线程

public class Producer implements Runnable {
    private final SharedResource resource;

    public Producer(SharedResource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            resource.produce();
        }
    }
}

消费者线程

public class Consumer implements Runnable {
    private final SharedResource resource;

    public Consumer(SharedResource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            resource.consume();
        }
    }
}

测试主类

public class ProducerConsumerDemo {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();

        Thread producerThread = new Thread(new Producer(resource), "Producer");
        Thread consumerThread = new Thread(new Consumer(resource), "Consumer");

        producerThread.start();
        consumerThread.start();
    }
}

运行输出示例

【生产】第 1 个商品
【生产】第 2 个商品
【生产】第 3 个商品
【生产】第 4 个商品
【生产】第 5 个商品
【消费】第 1 个商品
【消费】第 2 个商品
【消费】第 3 个商品
【消费】第 4 个商品
【消费】第 5 个商品
【生产】第 1 个商品
【生产】第 2 个商品
...

从输出可见,生产与消费交替进行,确保了资源的有效利用和线程间的协调运行。

总结

通过wait()notify()机制,Java实现了高效的线程间通信。这种协作模式广泛应用于缓冲区管理、任务队列、资源池等并发场景中,是构建稳定多线程应用的基础工具之一。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。