线程之间的通信

在多线程编程中,线程间的通信是实现协调和资源共享的核心机制。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

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

相关推荐
代码雕刻家2 分钟前
编程语法细节
java·c语言·开发语言
SimonKing5 分钟前
Agnes AI出桌面版了,可图可视频,免费用
java·后端·程序员
左左右右左右摇晃24 分钟前
手写Tomcat原理整理
java·开发语言·笔记·tomcat
孫治AllenSun28 分钟前
【JVM】四大引用类型分析
java·开发语言·jvm
于慨29 分钟前
安装java
java
夕除31 分钟前
sign 是什么
java·前端
Penina10181 小时前
代码如何导入idea
java·intellij idea
.Hypocritical.1 小时前
Maven笔记——2
java·笔记·maven
huohuopro1 小时前
手写 Tomcat 步骤教程
java·网络
snow@li1 小时前
Java:Lombok 完整讲解
java