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

在 Java 中,线程间通信(Inter-Thread Communication,简称 ITC)是指多个线程之间如何安全地交换数据、传递信号或协调执行顺序。

Java 提供了多种线程通信机制,从最基础的共享内存方式到高级并发工具,几乎覆盖了所有常见场景。下面按实用性和使用频率从高到低系统梳理,并附上核心代码示例和注意事项。

1. 共享内存 + 同步机制(最基础、最常用)

线程共享进程的堆内存,通过共享变量进行通信,但必须保证可见性原子性

方式可见性保证原子性保证适用场景推荐程度
volatile 变量有(内存屏障)无(仅单次读写)状态标志、开关、单次发布★★★★★
synchronized有(锁)简单同步块、保护共享变量★★★★
AtomicXXX(CAS)计数器、引用更新、标志位★★★★★
Lock + Condition需要显式条件等待/通知的场景★★★★

示例 — volatile + AtomicBoolean 实现开关

class SharedFlag {
    private volatile boolean running = true;           // 可见性
    private final AtomicBoolean stopped = new AtomicBoolean(false);

    public void stop() {
        stopped.set(true);
        running = false;
    }

    public boolean isRunning() {
        return running && !stopped.get();
    }
}

2. 等待/通知机制(经典生产者-消费者模式核心)

这是 Java 最传统、最重要的线程协作方式,基于 Object 的监视器(monitor)。

核心方法(必须在 synchronized 块/方法中使用):

  • wait() / wait(long timeout):释放锁,进入等待队列
  • notify():随机唤醒一个等待线程
  • notifyAll():唤醒所有等待线程

经典生产者-消费者示例(使用 wait/notify)

class MessageBox {
    private String message;
    private boolean empty = true;

    public synchronized String take() {
        while (empty) {           // 防止虚假唤醒,必须用 while
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        String msg = message;
        empty = true;
        notifyAll();              // 通知生产者可以继续生产
        return msg;
    }

    public synchronized void put(String msg) {
        while (!empty) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        message = msg;
        empty = false;
        notifyAll();              // 通知消费者可以消费
    }
}

现代替代方案java.util.concurrent.locks.Condition

class MessageBoxModern {
    private final Lock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull  = lock.newCondition();

    private String message;
    private boolean empty = true;

    public String take() throws InterruptedException {
        lock.lock();
        try {
            while (empty) {
                notEmpty.await();
            }
            String msg = message;
            empty = true;
            notFull.signalAll();
            return msg;
        } finally {
            lock.unlock();
        }
    }

    public void put(String msg) throws InterruptedException {
        lock.lock();
        try {
            while (!empty) {
                notFull.await();
            }
            message = msg;
            empty = false;
            notEmpty.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

为什么 Condition 更好?

  • 可以有多个等待队列(多个 Condition)
  • 支持 interruptible、timed await
  • 更灵活、更清晰

3. 高级并发工具类(JUC 包)—— 推荐在现代项目中使用

工具类通信方式典型场景线程安全
BlockingQueue(ArrayBlockingQueue, LinkedBlockingQueue)put / take生产者-消费者、线程池任务队列
SynchronousQueue直接交接无缓冲的线程间直接传递
Exchanger两线程交换数据两个线程互相交换数据(如并行排序)
CyclicBarrier / CountDownLatch线程等待计数或屏障多线程协作完成阶段性任务
Phaser更灵活的分阶段屏障动态参与者、阶段性任务
CompletionService / Future任务结果异步获取批量任务结果收集

最常用示例 — BlockingQueue 实现生产者消费者

BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

Producer: queue.put("data");   // 队列满则阻塞
Consumer: String data = queue.take();   // 队列空则阻塞

4. 其他通信方式(了解即可)

  • PipedInputStream / PipedOutputStream:线程间管道流(字节流)
  • join():一个线程等待另一个线程结束
  • Thread.interrupt() + isInterrupted():中断信号通信
  • 共享 volatile 数组 / 对象 + 自定义协议
  • LockSupport.park() / unpark():更底层的线程挂起/唤醒(Unsafe 级别)

5. 虚拟线程(Java 21+)下的线程通信(特别说明)

虚拟线程本质上是轻量级用户态线程,通信方式与平台线程完全相同

  • 仍然使用 wait/notifyConditionBlockingQueue
  • 阻塞操作(如 queue.take()Thread.sleep()Lock.lock())不会阻塞 carrier 线程(ForkJoinPool 的工作线程)
  • 推荐:优先使用 BlockingQueueCompletableFutureStructuredTaskScope(Java 21+)来组织协作,更符合结构化并发思想

虚拟线程时代推荐写法示例(Java 21+):

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    var scope = new StructuredTaskScope.ShutdownOnFailure();

    var task1 = scope.fork(() -> fetchDataFromDb());
    var task2 = scope.fork(() -> fetchDataFromApi());

    scope.join();           // 等待所有子任务完成
    scope.throwIfFailed();

    // 合并结果
    process(task1.get(), task2.get());
}

总结:线程间通信方式选择指南(2025–2026 推荐)

场景首选方式次选方式
简单状态标志、开关volatile / AtomicBoolean
生产者-消费者模型BlockingQueue(Linked / Array)wait/notify 或 Condition
需要多个条件等待ReentrantLock + 多 Condition
两个线程交换一次数据Exchanger
多线程等待某个条件完成CountDownLatch / CyclicBarrierPhaser
任务结果异步收集CompletableFuture / CompletionService
虚拟线程高并发协作StructuredTaskScope + virtual executorBlockingQueue

一句话总结

Java 线程间通信的核心是 “共享 + 同步”“消息传递”
现代项目中 优先使用 JUC 工具类(尤其是 BlockingQueue 和 StructuredTaskScope),能极大降低错误率并提高可读性。

如果你有具体场景(生产者消费者、线程池任务协调、虚拟线程下通信等),我可以给出更针对性的完整代码实现。

文章已创建 4631

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部