给我简述下线程间通信方式

你想了解 Java 中线程间通信的具体方式,我会从基础到进阶梳理 6 种核心通信方式,结合代码示例和适用场景,让你清楚每种方式的原理、用法和优缺点,同时区分"通信"(线程间交换数据/协调行为)和"同步"(线程间互斥)的核心差异。

核心结论先明确

Java 线程间通信的本质是线程间共享数据/协调执行顺序,核心方式分为两类:

  • 基础方式 :基于 Object 类的 wait()/notify()/notifyAll()(最原生)、join()sleep()(间接协调);
  • 进阶方式 :基于 Lock 锁的 ConditionBlockingQueue(生产消费模型)、CountDownLatch/CyclicBarrier(线程同步工具)、ThreadLocal(反向通信:线程内隔离)。

一、方式1:wait() + notify()/notifyAll()(最原生,基于对象监视器)

这是 Java 最基础的线程通信方式,依赖 Object 类的内置方法,核心是通过对象锁实现线程的等待/唤醒

核心原理
  • wait():线程释放对象锁,进入等待状态,直到被其他线程唤醒;
  • notify():唤醒等待该对象锁的任意一个线程;
  • notifyAll():唤醒等待该对象锁的所有线程;
  • 必须在 synchronized 代码块/方法中使用(持有对象锁才能调用)。
代码示例(生产消费模型)
java 复制代码
/**
 * 基于wait/notify的生产消费模型
 * 生产者生产数据,消费者消费数据,通过wait/notify协调
 */
public class WaitNotifyDemo {
    // 共享数据容器
    private final Object lock = new Object();
    private int data = 0;
    private boolean hasData = false; // 标记是否有数据

    // 生产者线程:生产数据
    class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                synchronized (lock) {
                    // 已有数据,等待消费者消费
                    while (hasData) {
                        try {
                            lock.wait(); // 释放锁,进入等待
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                    // 生产数据
                    data = i;
                    System.out.println("生产者生产:" + data);
                    hasData = true;
                    lock.notify(); // 唤醒消费者
                }
            }
        }
    }

    // 消费者线程:消费数据
    class Consumer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                synchronized (lock) {
                    // 无数据,等待生产者生产
                    while (!hasData) {
                        try {
                            lock.wait(); // 释放锁,进入等待
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                    // 消费数据
                    System.out.println("消费者消费:" + data);
                    hasData = false;
                    lock.notify(); // 唤醒生产者
                }
            }
        }
    }

    // 测试
    public static void main(String[] args) {
        WaitNotifyDemo demo = new WaitNotifyDemo();
        new Thread(demo.new Producer()).start();
        new Thread(demo.new Consumer()).start();
    }
}
输出结果(生产消费交替执行)
复制代码
生产者生产:0
消费者消费:0
生产者生产:1
消费者消费:1
生产者生产:2
消费者消费:2
生产者生产:3
消费者消费:3
生产者生产:4
消费者消费:4
适用场景
  • 简单的生产消费模型;
  • 线程间的基础等待/唤醒协调;
  • 缺点:只能唤醒任意/所有线程,无法精准唤醒指定线程。

二、方式2:Condition(基于Lock锁,精准通信)

Conditionjava.util.concurrent.locks 包下的接口,替代 wait()/notify(),核心优势是可以创建多个条件队列,精准唤醒指定线程

核心原理
  • lock.newCondition():创建一个条件对象;
  • condition.await():替代 wait(),释放锁并等待;
  • condition.signal():替代 notify(),唤醒该条件队列的一个线程;
  • condition.signalAll():替代 notifyAll(),唤醒该条件队列的所有线程;
  • 必须在 lock.lock()/lock.unlock() 之间使用。
代码示例(精准唤醒生产者/消费者)
java 复制代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 基于Condition的精准通信:分别唤醒生产者/消费者
 */
public class ConditionDemo {
    private final Lock lock = new ReentrantLock();
    private final Condition producerCond = lock.newCondition(); // 生产者条件
    private final Condition consumerCond = lock.newCondition(); // 消费者条件
    private int data = 0;
    private boolean hasData = false;

    // 生产者
    public void produce() {
        lock.lock();
        try {
            while (hasData) {
                producerCond.await(); // 生产者等待
            }
            data++;
            System.out.println("生产者生产:" + data);
            hasData = true;
            consumerCond.signal(); // 精准唤醒消费者
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
    }

    // 消费者
    public void consume() {
        lock.lock();
        try {
            while (!hasData) {
                consumerCond.await(); // 消费者等待
            }
            System.out.println("消费者消费:" + data);
            hasData = false;
            producerCond.signal(); // 精准唤醒生产者
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
    }

    // 测试
    public static void main(String[] args) {
        ConditionDemo demo = new ConditionDemo();
        // 生产者线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                demo.produce();
            }
        }).start();
        // 消费者线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                demo.consume();
            }
        }).start();
    }
}
适用场景
  • 需要精准唤醒指定线程的场景(如多生产者多消费者);
  • 替代 wait()/notify(),灵活性更高;
  • 优点:多条件队列,精准通信;缺点:需手动释放锁,易遗漏 unlock()

三、方式3:BlockingQueue(生产消费模型,最易用)

BlockingQueue(阻塞队列)是 Java 并发包提供的"开箱即用"通信工具,核心是队列满时生产者阻塞,队列空时消费者阻塞,无需手动处理等待/唤醒。

核心原理
  • 常用实现:ArrayBlockingQueue(数组实现,有界)、LinkedBlockingQueue(链表实现,可选有界);
  • put():队列满时阻塞生产者;
  • take():队列空时阻塞消费者;
  • 内置了 Condition 实现,底层仍是 lock + await()/signal()
代码示例(基于阻塞队列的生产消费)
java 复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 基于BlockingQueue的生产消费模型(无需手动处理等待/唤醒)
 */
public class BlockingQueueDemo {
    // 有界阻塞队列,容量为2
    private final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2);

    // 生产者
    class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    queue.put(i); // 队列满则阻塞
                    System.out.println("生产者生产:" + i + ",队列大小:" + queue.size());
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    // 消费者
    class Consumer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    int data = queue.take(); // 队列空则阻塞
                    System.out.println("消费者消费:" + data + ",队列大小:" + queue.size());
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    // 测试
    public static void main(String[] args) {
        BlockingQueueDemo demo = new BlockingQueueDemo();
        new Thread(demo.new Producer()).start();
        new Thread(demo.new Consumer()).start();
    }
}
输出结果(队列满时生产者阻塞)
复制代码
生产者生产:0,队列大小:1
生产者生产:1,队列大小:2
消费者消费:0,队列大小:1
生产者生产:2,队列大小:2
消费者消费:1,队列大小:1
生产者生产:3,队列大小:2
消费者消费:2,队列大小:1
生产者生产:4,队列大小:2
消费者消费:3,队列大小:1
消费者消费:4,队列大小:0
适用场景
  • 生产消费模型(最推荐的方式);
  • 线程间数据传递(如任务队列);
  • 优点:无需手动处理锁和等待/唤醒,代码简洁;缺点:仅适用于队列式通信。

四、方式4:join()(线程等待,简单协调)

join()Thread 类的方法,核心是让当前线程等待目标线程执行完毕后再继续,属于"被动通信"(通过等待实现顺序协调)。

核心原理
  • t.join():当前线程等待线程 t 执行完;
  • t.join(long millis):等待 millis 毫秒后超时继续。
代码示例(主线程等待子线程执行)
java 复制代码
/**
 * 基于join()的线程协调:主线程等待子线程执行完毕
 */
public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("子线程执行:" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });

        t1.start();
        t1.join(); // 主线程等待t1执行完毕
        System.out.println("主线程继续执行:子线程已完成");
    }
}
输出结果(主线程等待子线程)
复制代码
子线程执行:0
子线程执行:1
子线程执行:2
主线程继续执行:子线程已完成
适用场景
  • 简单的线程顺序执行(如主线程等待子线程加载数据);
  • 优点:使用简单;缺点:仅能实现"等待完成",无法传递数据。

五、方式5:并发工具类(CountDownLatch/CyclicBarrier,批量同步)

这类工具用于多线程批量同步,属于"间接通信",核心是协调多个线程的执行阶段。

1. CountDownLatch(倒计时门闩)
  • 原理:初始化一个计数器,线程完成任务后 countDown(),主线程 await() 等待计数器归0;
  • 场景:主线程等待多个子线程完成初始化。
2. CyclicBarrier(循环栅栏)
  • 原理:多个线程到达栅栏处 await(),等待所有线程到达后一起继续执行;
  • 场景:多线程分阶段执行任务(如所有线程完成第一阶段后,再一起执行第二阶段)。
代码示例(CountDownLatch)
java 复制代码
import java.util.concurrent.CountDownLatch;

/**
 * CountDownLatch:主线程等待3个子线程完成任务
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3); // 计数器=3

        for (int i = 0; i < 3; i++) {
            int taskId = i;
            new Thread(() -> {
                System.out.println("子线程" + taskId + "执行任务");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                latch.countDown(); // 计数器-1
            }).start();
        }

        latch.await(); // 主线程等待计数器归0
        System.out.println("所有子线程完成任务,主线程继续");
    }
}
适用场景
  • 多线程初始化、批量任务执行;
  • 优点:批量同步简单;缺点:CountDownLatch 计数器不可重置,CyclicBarrier 可循环使用。

六、方式6:ThreadLocal(反向通信:线程内隔离)

ThreadLocal 严格来说是"线程内数据隔离",但也可视为一种特殊的"通信"------线程内部的不同方法间共享数据,避免线程间数据干扰

核心原理
  • ThreadLocal<T>:为每个线程创建独立的变量副本;
  • set(T):设置当前线程的变量副本;
  • get():获取当前线程的变量副本;
  • remove():移除当前线程的变量副本(避免内存泄漏)。
代码示例(ThreadLocal 线程内共享数据)
java 复制代码
/**
 * ThreadLocal:线程内共享数据,线程间隔离
 */
public class ThreadLocalDemo {
    // 定义ThreadLocal变量,存储线程专属数据
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        // 线程1:设置并获取自己的副本
        new Thread(() -> {
            threadLocal.set("线程1的专属数据");
            System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            threadLocal.remove(); // 用完移除
        }, "线程1").start();

        // 线程2:设置并获取自己的副本
        new Thread(() -> {
            threadLocal.set("线程2的专属数据");
            System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            threadLocal.remove(); // 用完移除
        }, "线程2").start();
    }
}
输出结果(线程间数据隔离)
复制代码
线程1:线程1的专属数据
线程2:线程2的专属数据
适用场景
  • 线程内共享数据(如用户登录信息、数据库连接);
  • 避免多线程共享变量的同步问题;
  • 优点:线程安全,无需加锁;缺点:易引发内存泄漏(需手动 remove())。

七、各通信方式对比(快速选型)

方式 核心优势 适用场景 缺点
wait()/notify() 原生,无需额外依赖 简单生产消费 无法精准唤醒,需手动处理锁
Condition 精准唤醒指定线程 多生产者多消费者 需手动释放锁
BlockingQueue 开箱即用,无需手动处理同步 生产消费模型(推荐) 仅适用于队列式通信
join() 简单的线程顺序协调 主线程等待子线程完成 无法传递数据
CountDownLatch/栅栏 批量线程同步 多线程初始化、分阶段执行 CountDownLatch 计数器不可重置
ThreadLocal 线程内数据隔离,无需加锁 线程内共享数据(如用户上下文) 易内存泄漏,需手动 remove

总结

  1. 生产消费模型优先选 BlockingQueue:代码简洁,无需手动处理等待/唤醒,是工业界主流;
  2. 精准唤醒选 Condition :替代 wait()/notify(),灵活性更高;
  3. 简单顺序协调选 join():适用于主线程等待子线程完成;
  4. 批量同步选 CountDownLatch/CyclicBarrier:适配多线程分阶段执行;
  5. 线程内数据共享选 ThreadLocal:避免同步问题,注意内存泄漏。

如果需要某一种方式的进阶用法(如多生产者多消费者的 Condition 实现、BlockingQueue 的性能优化),可以告诉我,我会补充详细示例。

相关推荐
UrbanJazzerati3 小时前
一文介绍PostgreSQL与基本架构
后端·面试
SuperEugene3 小时前
对象数组的排序与分组:sort / localeCompare / 自定义 compare
前端·javascript·面试
千寻girling6 小时前
面试官 : “ 请问你实际开发中用过 函数柯理化 吗? 能讲一下吗 ?”
前端·javascript·面试
xiaoye-duck8 小时前
C++ 模板进阶:从非类型参数、特化到分离编译,吃透 C++ 泛型编程的核心逻辑
c++·面试·模板
知识即是力量ol8 小时前
口语八股—— Spring 面试实战指南(终篇):常用注解篇、Spring中的设计模式
java·spring·设计模式·面试·八股·常用注解
木斯佳9 小时前
前端八股文面经大全:腾讯WXG技术架构前端面试(2025-11-19)·面经深度解析
前端·面试·架构
2501_901147839 小时前
DDP(分布式训练)核心知识点学习笔记
笔记·分布式·学习·面试
千寻girling10 小时前
面试官 : “ 请说一下 JS 的常见的数组 和 字符串方法有哪些 ? ”
前端·javascript·面试
无心水10 小时前
6、合纵连横:开源快速开发平台全解析与自建平台架构实战【终篇】
java·后端·科技·spring·面试·架构·开源