在Java多线程编程中,线程通信是指多个线程之间通过某种方式交换信息或协调执行顺序的过程。不同于进程间通信(IPC)需要借助操作系统提供的机制,Java线程通信建立在共享内存模型基础上,这使得通信效率更高但同时也带来了线程安全的挑战。
Java线程通信的核心是共享内存模型,所有线程共享堆内存和方法区内存。这种设计带来了两个关键特性:
可见性问题:由于现代CPU的多级缓存架构,一个线程对共享变量的修改可能不会立即被其他线程看到。例如:
java复制// 共享变量
boolean flag = false;
// 线程A
flag = true; // 修改可能不会立即写入主内存
// 线程B
while(!flag); // 可能永远看不到线程A的修改
原子性问题:看似简单的操作在底层可能是多个指令的组合。例如i++实际上包含读取、修改、写入三个步骤,在多线程环境下可能导致竞态条件。
重要提示:理解这些底层特性是掌握线程通信的基础,所有高级通信机制本质上都是在解决这些问题。
Java提供了多种线程通信机制,根据抽象层次可以分为三大类:
基础同步机制:
synchronized关键字volatile关键字wait()/notify()机制并发工具类:
BlockingQueue等阻塞队列CountDownLatch等同步器高级并发框架:
Executor框架Fork/Join框架wait()和notify()是Java中最基础的线程协作机制,它们必须配合synchronized使用,因为:
wait()会释放锁并让线程进入WAITING状态标准使用模板:
java复制synchronized(lock) {
while(条件不满足) { // 必须用while而不是if
lock.wait();
}
// 执行操作
lock.notifyAll(); // 或notify()
}
让我们扩展原始示例,实现一个更健壮的生产者-消费者模型:
java复制class MessageQueue {
private final Queue<String> queue = new LinkedList<>();
private final int maxSize;
public MessageQueue(int maxSize) {
this.maxSize = maxSize;
}
public synchronized void produce(String message) throws InterruptedException {
while(queue.size() == maxSize) {
wait(); // 队列满时等待
}
queue.add(message);
notifyAll(); // 唤醒所有等待线程
}
public synchronized String consume() throws InterruptedException {
while(queue.isEmpty()) {
wait(); // 队列空时等待
}
String message = queue.poll();
notifyAll(); // 唤醒所有等待线程
return message;
}
}
关键改进点:
notifyAll()而非notify()避免线程饥饿陷阱1:虚假唤醒
陷阱2:丢失唤醒
最佳实践:
notifyAll()除非能确保只唤醒一个正确线程wait(1000)Condition接口提供更灵活的等待/通知机制| 实现类 | 数据结构 | 边界策略 | 适用场景 |
|---|---|---|---|
| ArrayBlockingQueue | 数组 | 有界 | 固定大小队列 |
| LinkedBlockingQueue | 链表 | 可选有界 | 高吞吐量场景 |
| PriorityBlockingQueue | 堆 | 无界 | 需要优先级的任务调度 |
| SynchronousQueue | 不存储元素 | 直接传递 | 手递手传递场景 |
批量操作示例:
java复制BlockingQueue<Data> queue = new LinkedBlockingQueue<>(100);
// 生产者批量提交
List<Data> batch = fetchDataBatch();
queue.addAll(batch); // 可能阻塞
// 消费者批量获取
List<Data> consumed = new ArrayList<>(10);
queue.drainTo(consumed, 10); // 非阻塞获取最多10个元素
拒绝策略实现:
java复制BlockingQueue<Task> queue = new ArrayBlockingQueue<>(100);
public void submitTask(Task task) {
if(!queue.offer(task)) { // 非阻塞尝试添加
// 队列满时的处理策略
log.warn("Queue full, rejecting task");
throw new RejectedExecutionException("Queue capacity exceeded");
}
}
根据场景选择合适的队列实现:
LinkedBlockingQueueArrayBlockingQueuePriorityBlockingQueue合理设置队列容量:
监控队列使用情况:
java复制// 获取队列剩余容量
int remaining = queue.remainingCapacity();
// 获取队列当前大小
int size = queue.size();
典型应用场景:
高级用法示例:
java复制// 多阶段任务控制
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch endLatch = new CountDownLatch(10);
for(int i=0; i<10; i++) {
new Thread(() -> {
try {
startLatch.await(); // 等待开始信号
doWork();
} finally {
endLatch.countDown(); // 完成计数
}
}).start();
}
// 主线程控制
long start = System.nanoTime();
startLatch.countDown(); // 同时释放所有线程
endLatch.await(); // 等待所有完成
long duration = System.nanoTime() - start;
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 重置 | 不可重置 | 可循环使用 |
| 计数方向 | 递减 | 递增 |
| 等待机制 | 线程等待计数归零 | 线程互相等待 |
| 异常处理 | 无特殊机制 | 提供屏障动作 |
| 典型用途 | 启动/停止同步 | 分阶段任务 |
CyclicBarrier示例:
java复制class MatrixSolver {
final int N;
final float[][] data;
final CyclicBarrier barrier;
class Worker implements Runnable {
int myRow;
Worker(int row) { myRow = row; }
public void run() {
while(!done()) {
processRow(myRow);
try {
barrier.await(); // 等待所有行处理完成
} catch (Exception e) {
return;
}
}
}
}
public MatrixSolver(float[][] matrix) {
data = matrix;
N = matrix.length;
barrier = new CyclicBarrier(N, () -> {
mergeRows(); // 所有行处理完成后执行
});
for(int i=0; i<N; i++)
new Thread(new Worker(i)).start();
}
}
连接池实现示例:
java复制class ConnectionPool {
private final Semaphore semaphore;
private final BlockingQueue<Connection> pool;
public ConnectionPool(int size) {
semaphore = new Semaphore(size);
pool = new LinkedBlockingQueue<>(size);
for(int i=0; i<size; i++) {
pool.add(createConnection());
}
}
public Connection getConnection() throws InterruptedException {
semaphore.acquire(); // 获取许可
return pool.take();
}
public void releaseConnection(Connection conn) {
pool.offer(conn);
semaphore.release(); // 释放许可
}
}
公平性控制:
java复制// 创建公平信号量
Semaphore fairSemaphore = new Semaphore(10, true); // true表示公平模式
减少锁竞争:
ReentrantReadWriteLock)替代独占锁ConcurrentHashMap, AtomicInteger)避免过早优化:
jstack和JProfiler定位真正的瓶颈上下文切换成本:
交叉锁死锁:
java复制// 线程A
synchronized(lock1) {
synchronized(lock2) { ... }
}
// 线程B
synchronized(lock2) {
synchronized(lock1) { ... }
}
解决方案:
tryLock()带超时机制jstack检测死锁线程转储分析:
bash复制# 获取线程转储
jstack <pid> > thread_dump.txt
# 查找死锁
grep -A 10 "deadlock" thread_dump.txt
JConsole监控:
链式调用示例:
java复制CompletableFuture.supplyAsync(() -> fetchOrder())
.thenApplyAsync(order -> processOrder(order))
.thenAcceptAsync(result -> sendNotification(result))
.exceptionally(ex -> {
log.error("Error occurred", ex);
return null;
});
基于Project Reactor的示例:
java复制Flux.range(1, 10)
.parallel()
.runOn(Schedulers.parallel())
.map(i -> i * 2)
.sequential()
.subscribe(System.out::println);
Java 19+虚拟线程示例:
java复制try(var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // 自动等待所有任务完成
在实际项目中,我发现合理选择线程通信机制可以显著提升系统性能和可维护性。对于简单的同步需求,synchronized+wait/notify可能就足够了;对于复杂的生产者-消费者场景,BlockingQueue通常是更好的选择;而需要更精细控制时,CountDownLatch和CyclicBarrier等工具类能提供更大的灵活性。最重要的是,无论选择哪种机制,都要确保充分理解其语义和潜在的性能影响。