线程之间的通信

在多线程编程中,线程间的通信是实现协调和资源共享的核心机制。Java 提供了多种线程通信方式,主要分为 共享内存消息传递 两大类。以下是详细的分类、原理及使用场景解析。


一、共享内存通信

通过共享变量实现线程间数据交换,依赖 同步机制 保证可见性和原子性。


1. synchronized + wait()/notify()

  • 原理

    • wait():释放锁并进入等待状态,线程挂起。
    • notify()/notifyAll():唤醒一个或所有等待线程。
    • 必须与 synchronized 配合使用 (否则抛出 IllegalMonitorStateException)。
  • 示例(生产者-消费者模型):

    arduino 复制代码
    public class Buffer {
        private Queue<Integer> queue = new LinkedList<>();
        private int capacity = 10;
    ​
        public synchronized void produce(int item) throws InterruptedException {
            while (queue.size() == capacity) {
                wait(); // 缓冲区满,等待
            }
            queue.add(item);
            notifyAll(); // 唤醒消费者
        }
    ​
        public synchronized int consume() throws InterruptedException {
            while (queue.isEmpty()) {
                wait(); // 缓冲区空,等待
            }
            int item = queue.poll();
            notifyAll(); // 唤醒生产者
            return item;
        }
    }
  • 适用场景:简单的线程协作(如资源池、任务队列)。


2. Lock + Condition

  • 原理

    • Lock 替代 synchronized,提供更灵活的锁控制。
    • Condition 替代 wait()/notify(),支持多条件队列。
  • 示例

    csharp 复制代码
    public class BufferWithLock {
        private final Lock lock = new ReentrantLock();
        private final Condition notFull = lock.newCondition();
        private final Condition notEmpty = lock.newCondition();
        private Queue<Integer> queue = new LinkedList<>();
        private int capacity = 10;
    ​
        public void produce(int item) throws InterruptedException {
            lock.lock();
            try {
                while (queue.size() == capacity) {
                    notFull.await(); // 等待"不满"条件
                }
                queue.add(item);
                notEmpty.signal(); // 触发"非空"条件
            } finally {
                lock.unlock();
            }
        }
    ​
        public int consume() throws InterruptedException {
            lock.lock();
            try {
                while (queue.isEmpty()) {
                    notEmpty.await(); // 等待"非空"条件
                }
                int item = queue.poll();
                notFull.signal(); // 触发"不满"条件
                return item;
            } finally {
                lock.unlock();
            }
        }
    }
  • 优势

    • 支持多条件等待(如区分生产者和消费者队列)。
    • 可中断锁、超时等待等高级功能。
  • 适用场景:复杂条件同步(如读写锁、多状态资源池)。


二、消息传递通信

通过中间载体传递数据,减少直接共享内存的复杂性。


1. BlockingQueue

  • 原理 :线程安全的阻塞队列,提供 put()(阻塞插入)和 take()(阻塞获取)方法。

  • 示例(简化生产者-消费者):

    arduino 复制代码
    public class BlockingQueueDemo {
        private BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
    ​
        public void produce(int item) throws InterruptedException {
            queue.put(item); // 队列满时自动阻塞
        }
    ​
        public int consume() throws InterruptedException {
            return queue.take(); // 队列空时自动阻塞
        }
    }
  • 优势:无需手动同步,内置线程安全。

  • 适用场景:高并发生产者-消费者模型(如线程池任务队列)。


2. PipedInputStream/PipedOutputStream

  • 原理:通过管道连接输入输出流,实现线程间字节数据传输。

  • 示例

    scss 复制代码
    public class PipedCommunication {
        public static void main(String[] args) throws IOException {
            PipedInputStream pis = new PipedInputStream();
            PipedOutputStream pos = new PipedOutputStream();
            pis.connect(pos); // 连接管道
    ​
            // 生产者线程写数据
            new Thread(() -> {
                try {
                    pos.write("Hello from thread!".getBytes());
                    pos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
    ​
            // 消费者线程读数据
            new Thread(() -> {
                try {
                    int data;
                    while ((data = pis.read()) != -1) {
                        System.out.print((char) data);
                    }
                    pis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
  • 适用场景:线程间流式数据传输(较少使用,性能较低)。


三、线程协作工具

通过工具类协调多个线程的执行顺序。


1. CountDownLatch

  • 原理:计数器归零时触发动作,不可重复使用。

  • 示例(等待多个线程完成):

    arduino 复制代码
    public class CountDownLatchDemo {
        public static void main(String[] args) throws InterruptedException {
            int threadCount = 3;
            CountDownLatch latch = new CountDownLatch(threadCount);
    ​
            for (int i = 0; i < threadCount; i++) {
                new Thread(() -> {
                    System.out.println("Task completed");
                    latch.countDown();
                }).start();
            }
    ​
            latch.await(); // 等待所有线程完成
            System.out.println("All tasks done");
        }
    }
  • 适用场景:主线程等待子线程初始化完成。


2. CyclicBarrier

  • 原理:所有线程到达屏障后触发回调,可重复使用。

  • 示例(多阶段任务同步):

    csharp 复制代码
    public class CyclicBarrierDemo {
        public static void main(String[] args) {
            int threadCount = 3;
            CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
                System.out.println("All threads reached the barrier");
            });
    ​
            for (int i = 0; i < threadCount; i++) {
                new Thread(() -> {
                    try {
                        System.out.println("Thread waiting");
                        barrier.await(); // 等待其他线程
                        System.out.println("Thread resumed");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        }
    }
  • 适用场景:多线程分阶段协作(如并行计算)。


3. Exchanger

  • 原理:两个线程在屏障点交换数据。

  • 示例

    ini 复制代码
    public class ExchangerDemo {
        public static void main(String[] args) {
            Exchanger<String> exchanger = new Exchanger<>();
    ​
            new Thread(() -> {
                try {
                    String data = "Data from Thread A";
                    String received = exchanger.exchange(data);
                    System.out.println("Thread A received: " + received);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
    ​
            new Thread(() -> {
                try {
                    String data = "Data from Thread B";
                    String received = exchanger.exchange(data);
                    System.out.println("Thread B received: " + received);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
  • 适用场景:成对线程数据交换(如校对计算结果)。


四、总结与对比

通信方式 原理 优点 缺点 适用场景
synchronized 锁 + 等待/唤醒 简单易用 灵活性差,无法多条件等待 简单同步协作
Lock+Condition 显式锁 + 多条件 灵活支持多条件 需手动释放锁 复杂条件同步
BlockingQueue 阻塞队列传递数据 内置线程安全,解耦生产消费 仅适合特定模式 生产者-消费者模型
CountDownLatch 计数器等待完成 一次性协调多线程 不可重复使用 主线程等待子线程初始化
CyclicBarrier 可重复使用的屏障 支持重复执行 回调可能影响性能 多阶段并行计算

五、选择建议

  1. 简单协作 :优先使用 synchronizedBlockingQueue
  2. 复杂条件 :选择 Lock + Condition
  3. 多线程协调 :根据需求选用 CountDownLatchCyclicBarrier
  4. 数据交换 :特定场景使用 Exchanger

理解线程通信机制是构建高效、健壮并发程序的基础,合理选择通信方式能显著提升代码可维护性和性能。

相关推荐
在京奋斗者几秒前
spring boot自动装配原理
java·spring boot·spring
明天不下雨(牛客同名)3 小时前
为什么 ThreadLocalMap 的 key 是弱引用 value是强引用
java·jvm·算法
多多*3 小时前
Java设计模式 简单工厂模式 工厂方法模式 抽象工厂模式 模版工厂模式 模式对比
java·linux·运维·服务器·stm32·单片机·嵌入式硬件
胡图蛋.5 小时前
Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?
java·spring boot·后端
牛马baby5 小时前
Java高频面试之并发编程-01
java·开发语言·面试
小小大侠客5 小时前
将eclipse中的web项目导入idea
java·eclipse·intellij-idea
不再幻想,脚踏实地5 小时前
MySQL(一)
java·数据库·mysql
吃海鲜的骆驼5 小时前
SpringBoot详细教程(持续更新中...)
java·spring boot·后端
迷雾骑士6 小时前
SpringBoot中WebMvcConfigurer注册多个拦截器(addInterceptors)时的顺序问题(二)
java·spring boot·后端·interceptor
别来无恙✲6 小时前
Mybatis源码分析
java·源码分析