在多线程编程中,线程间的通信是实现协调和资源共享的核心机制。Java 提供了多种线程通信方式,主要分为 共享内存 和 消息传递 两大类。以下是详细的分类、原理及使用场景解析。
一、共享内存通信
通过共享变量实现线程间数据交换,依赖 同步机制 保证可见性和原子性。
1. synchronized + wait()/notify()
-
原理:
wait()
:释放锁并进入等待状态,线程挂起。notify()
/notifyAll()
:唤醒一个或所有等待线程。- 必须与
synchronized
配合使用 (否则抛出IllegalMonitorStateException
)。
-
示例(生产者-消费者模型):
arduinopublic 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()
,支持多条件队列。
-
示例:
csharppublic 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()
(阻塞获取)方法。 -
示例(简化生产者-消费者):
arduinopublic 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
-
原理:通过管道连接输入输出流,实现线程间字节数据传输。
-
示例:
scsspublic 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
-
原理:计数器归零时触发动作,不可重复使用。
-
示例(等待多个线程完成):
arduinopublic 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
-
原理:所有线程到达屏障后触发回调,可重复使用。
-
示例(多阶段任务同步):
csharppublic 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
-
原理:两个线程在屏障点交换数据。
-
示例:
inipublic 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 | 可重复使用的屏障 | 支持重复执行 | 回调可能影响性能 | 多阶段并行计算 |
五、选择建议
- 简单协作 :优先使用
synchronized
或BlockingQueue
。 - 复杂条件 :选择
Lock + Condition
。 - 多线程协调 :根据需求选用
CountDownLatch
或CyclicBarrier
。 - 数据交换 :特定场景使用
Exchanger
。
理解线程通信机制是构建高效、健壮并发程序的基础,合理选择通信方式能显著提升代码可维护性和性能。