如何在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();

    }

}

AI写代码


关键解释:


wait()必须在synchronized块中调用,因为调用前需要先获取对象锁,调用后会释放锁(这是和sleep()的核心区别:sleep()不会释放锁)。

用while判断条件而非if:防止 “虚假唤醒”(线程被唤醒后,条件可能已经不满足,需要重新检查)。

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();

    }

}

AI写代码


核心优势:


可以创建多个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();

    }

}

AI写代码


输出效果:


plaintext


生产者1 生产了产品1,队列大小:1

消费者1 消费了产品1,队列大小:0

生产者1 生产了产品2,队列大小:1

消费者1 消费了产品2,队列大小:0

...(生产者和消费者交替执行)

AI写代码

核心优势:


无需手动处理锁和等待 / 唤醒,代码极简,不易出错。

支持多种队列类型:ArrayBlockingQueue(数组实现)、LinkedBlockingQueue(链表实现)、SynchronousQueue(无容量队列)等。

三、核心方法对比

方式 核心 API 优点 缺点

wait()/notify() Object 类方法 基础经典,无需额外依赖 只能随机唤醒,需手动处理锁,易出错

Condition Lock+Condition 精准唤醒,锁更灵活 代码稍复杂,需手动释放锁

BlockingQueue put()/take() 极简高效,生产环境首选 依赖 JUC 包,灵活性稍低(适合标准生产消费场景)

总结

线程间通信的核心是共享资源 + 等待 / 唤醒,目的是让线程协同工作而非抢占资源。

入门学习用wait()/notify()理解原理,进阶用Condition实现精准控制,生产环境优先用BlockingQueue(极简且不易出错)。

关键注意点:wait()会释放锁,sleep()不会;必须在锁保护的代码块中使用等待 / 唤醒方法;用while判断条件防止虚假唤醒。



下一篇: 没有了