如何在Java中实现线程间的通信?

一、线程间通信的核心场景

最典型的场景是生产者 - 消费者模型

  • 生产者线程:生产数据(往共享容器里放数据)
  • 消费者线程:消费数据(从共享容器里取数据)
  • 通信需求:容器满时生产者等待,容器空时消费者等待;生产 / 消费后唤醒对方。

二、Java 实现线程通信的 3 种核心方式

方式 1:使用 Object 类的 wait ()/notify ()/notifyAll ()(基础经典)

这是基于对象监视器(锁) 的通信方式,必须在synchronized代码块 / 方法中使用:

  • wait():让当前线程释放锁并进入等待状态,直到被唤醒。
  • notify():唤醒等待该对象锁的一个线程(随机)。
  • notifyAll():唤醒等待该对象锁的所有线程(推荐,避免线程永久等待)。

完整示例(生产者 - 消费者)

java

运行

复制代码
// 共享资源:仓库(最多存1个产品)
class Warehouse {
    private int product = 0; // 产品数量

    // 生产产品(生产者调用)
    public synchronized void produce() {
        // 仓库已满,生产者等待
        while (product >= 1) { // 用while而非if,防止虚假唤醒
            try {
                wait(); // 释放锁,进入等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 生产产品
        product++;
        System.out.println(Thread.currentThread().getName() + " 生产了产品,当前库存:" + product);
        notifyAll(); // 唤醒消费者
    }

    // 消费产品(消费者调用)
    public synchronized void consume() {
        // 仓库为空,消费者等待
        while (product <= 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 消费产品
        product--;
        System.out.println(Thread.currentThread().getName() + " 消费了产品,当前库存:" + product);
        notifyAll(); // 唤醒生产者
    }
}

// 生产者线程
class Producer implements Runnable {
    private Warehouse warehouse;

    public Producer(Warehouse warehouse) {
        this.warehouse = warehouse;
    }

    @Override
    public void run() {
        // 循环生产5次
        for (int i = 0; i < 5; i++) {
            warehouse.produce();
            try {
                Thread.sleep(500); // 模拟生产耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 消费者线程
class Consumer implements Runnable {
    private Warehouse warehouse;

    public Consumer(Warehouse warehouse) {
        this.warehouse = warehouse;
    }

    @Override
    public void run() {
        // 循环消费5次
        for (int i = 0; i < 5; i++) {
            warehouse.consume();
            try {
                Thread.sleep(800); // 模拟消费耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 测试类
public class WaitNotifyDemo {
    public static void main(String[] args) {
        Warehouse warehouse = new Warehouse();
        // 启动生产者和消费者线程
        new Thread(new Producer(warehouse), "生产者1").start();
        new Thread(new Consumer(warehouse), "消费者1").start();
    }
}

关键解释

  1. wait()必须在synchronized块中调用,因为调用前需要先获取对象锁,调用后会释放锁 (这是和sleep()的核心区别:sleep()不会释放锁)。
  2. while判断条件而非if:防止 "虚假唤醒"(线程被唤醒后,条件可能已经不满足,需要重新检查)。
  3. notifyAll()notify()更安全:避免只唤醒同类线程(比如生产者唤醒生产者)导致死锁。
方式 2:使用 Condition 接口(JUC 包,更灵活)

Condition是 Java 并发包(java.util.concurrent)提供的增强版等待 / 唤醒机制,基于Lock锁实现,相比wait()/notify()更灵活(可以创建多个 Condition,实现精准唤醒)。

完整示例

java

运行

复制代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 共享资源:仓库
class Warehouse2 {
    private int product = 0;
    private final Lock lock = new ReentrantLock(); // 可重入锁
    private final Condition producerCondition = lock.newCondition(); // 生产者条件
    private final Condition consumerCondition = lock.newCondition(); // 消费者条件

    // 生产产品
    public void produce() {
        lock.lock(); // 获取锁
        try {
            while (product >= 1) {
                producerCondition.await(); // 生产者等待(替代wait())
            }
            product++;
            System.out.println(Thread.currentThread().getName() + " 生产了产品,当前库存:" + product);
            consumerCondition.signal(); // 精准唤醒消费者(替代notify())
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 释放锁(必须在finally中,防止异常导致锁不释放)
        }
    }

    // 消费产品
    public void consume() {
        lock.lock();
        try {
            while (product <= 0) {
                consumerCondition.await(); // 消费者等待
            }
            product--;
            System.out.println(Thread.currentThread().getName() + " 消费了产品,当前库存:" + product);
            producerCondition.signal(); // 精准唤醒生产者
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

// 测试类
public class ConditionDemo {
    public static void main(String[] args) {
        Warehouse2 warehouse = new Warehouse2();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                warehouse.produce();
                try { Thread.sleep(500); } catch (InterruptedException e) {}
            }
        }, "生产者1").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                warehouse.consume();
                try { Thread.sleep(800); } catch (InterruptedException e) {}
            }
        }, "消费者1").start();
    }
}

核心优势

  • 可以创建多个Condition对象,实现 "精准唤醒"(比如只唤醒生产者 / 消费者),而notify()是随机唤醒。
  • Lock锁的获取和释放更灵活(可以在非代码块中使用),且支持尝试获取锁(tryLock())。
方式 3:使用 BlockingQueue(阻塞队列,最高效)

BlockingQueue是 JUC 包提供的阻塞队列,内置了线程通信逻辑,无需手动写 wait/notify,是生产环境中最推荐的方式。

核心特性:

  • 队列满时,入队操作(put())会阻塞生产者线程。
  • 队列空时,出队操作(take())会阻塞消费者线程。

完整示例

java

运行

复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

// 测试类(无需自定义仓库,直接用BlockingQueue)
public class BlockingQueueDemo {
    // 创建容量为1的阻塞队列(模拟仓库)
    private static final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1);

    public static void main(String[] args) {
        // 生产者线程:往队列里放数据
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    queue.put(i); // 队列满时阻塞
                    System.out.println(Thread.currentThread().getName() + " 生产了产品" + i + ",队列大小:" + queue.size());
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者1").start();

        // 消费者线程:从队列里取数据
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    Integer product = queue.take(); // 队列空时阻塞
                    System.out.println(Thread.currentThread().getName() + " 消费了产品" + product + ",队列大小:" + queue.size());
                    Thread.sleep(800);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者1").start();
    }
}

输出效果

plaintext

复制代码
生产者1 生产了产品1,队列大小:1
消费者1 消费了产品1,队列大小:0
生产者1 生产了产品2,队列大小:1
消费者1 消费了产品2,队列大小:0
...(生产者和消费者交替执行)

核心优势

  • 无需手动处理锁和等待 / 唤醒,代码极简,不易出错。
  • 支持多种队列类型:ArrayBlockingQueue(数组实现)、LinkedBlockingQueue(链表实现)、SynchronousQueue(无容量队列)等。

三、核心方法对比

方式 核心 API 优点 缺点
wait()/notify() Object 类方法 基础经典,无需额外依赖 只能随机唤醒,需手动处理锁,易出错
Condition Lock+Condition 精准唤醒,锁更灵活 代码稍复杂,需手动释放锁
BlockingQueue put()/take() 极简高效,生产环境首选 依赖 JUC 包,灵活性稍低(适合标准生产消费场景)

总结

  1. 线程间通信的核心是共享资源 + 等待 / 唤醒,目的是让线程协同工作而非抢占资源。
  2. 入门学习用wait()/notify()理解原理,进阶用Condition实现精准控制,生产环境优先用BlockingQueue(极简且不易出错)。
  3. 关键注意点:wait()会释放锁,sleep()不会;必须在锁保护的代码块中使用等待 / 唤醒方法;用while判断条件防止虚假唤醒。
相关推荐
这儿有个昵称2 小时前
Java面试场景:从音视频到微服务的技术深挖
java·spring boot·spring cloud·微服务·面试·kafka·音视频
modelmd2 小时前
Go、Java 的值类型和引用类型对比
java·golang
移远通信2 小时前
短信的应用
java·git·python
a努力。2 小时前
阿里Java面试被问:WebSocket的心跳检测和自动重连实现
java·开发语言·python·websocket·面试·职场和发展·哈希算法
冷雨夜中漫步2 小时前
Python入门——__init__.py文件作用
android·java·python
Volunteer Technology2 小时前
Centos7安装python和jupyter
linux·python·jupyter
deng12042 小时前
【排序算法总结(1)】
java·算法·排序算法
小宋10212 小时前
Kafka 自动发送消息 Demo 实战:从配置到发送的完整流程(java)
java·分布式·kafka
Lansonli2 小时前
大数据Spark(七十七):Action行动算子first、collect和collectAsMap使用案例
大数据·分布式·spark