一、什么是多线程,为什么要用?
在 Java 程序中,线程(Thread) 是操作系统分配 CPU 时间的最小单位。一个进程可以包含多个线程,它们共享进程的内存空间,却能同时执行不同的任务。
为什么要用多线程?
- 提升性能与吞吐量
当程序中存在 I/O 阻塞(例如网络、磁盘读写)时,单线程会等待。多线程允许其他线程继续执行,提升 CPU 利用率。 - 并行处理任务
比如同时处理多个用户请求、多个计算任务。 - 提升响应速度
Web 服务中,一个线程可以处理用户的请求,另一个线程去加载缓存或异步写日志。
简单理解:
多线程是把"等待的时间"利用起来,让程序更"聪明"地使用 CPU。
二、单体应用中的多线程使用方式
在传统的单体应用中,Java 提供了多种实现方式:
1. 直接使用 Thread
java
new Thread(() -> {
System.out.println("执行异步任务");
}).start();
适用场景 :简单、短生命周期任务。
问题:
- 无法管理线程生命周期;
- 容易造成线程过多,内存占用大。
2. 使用 Runnable + ExecutorService
java
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(() -> doWork());
适用场景 :需要并发控制、任务调度。
问题:
- 线程池参数配置不当容易导致 OOM;
- 任务阻塞时整个线程池卡死;
- 异常难以捕获。
解决方案:
- 用
ThreadPoolExecutor手动设置核心线程数、队列长度; - 使用
Future获取任务结果; - 配合
RejectedExecutionHandler处理拒绝策略。
3. 使用 CompletableFuture
java
CompletableFuture.supplyAsync(() -> getData())
.thenApply(data -> process(data))
.thenAccept(result -> save(result));
适用场景 :异步编排、依赖任务链。
问题:
- 多个任务之间的数据共享复杂;
- 异常传播难;
- 默认线程池为
ForkJoinPool.commonPool(),不易监控。
解决方案:
- 使用自定义线程池;
- 用
handle()统一异常; - 使用链式调用保持可读性。
4. 使用 Spring 的 @Async
java
@Async
public void sendEmail(String msg) { ... }
适用场景 :异步通知、日志、外部接口调用。
问题:
@Async默认线程池小;- 异步方法之间不能相互调用(同类内调用失效);
- 返回值要用
Future或CompletableFuture包装。
解决方案:
- 自定义
TaskExecutor; - 避免在同类中直接调用;
- 使用
@EnableAsync开启配置。
5. 使用定时任务线程池
@Scheduled 或 ScheduledExecutorService
适合周期性任务,比如定时清理缓存。
问题:
- 长时间阻塞会影响下一次调度;
- 异常会中断后续执行。
解决方案:
- 捕获异常;
- 分离业务逻辑与调度逻辑。
三、线程数据共享与一致性问题
线程之间共享内存是性能的来源,也是问题的根源。
常见问题:
-
可见性问题
一个线程修改变量,另一个线程看不到。
- 解决方案:使用
volatile或同步机制。
- 解决方案:使用
-
原子性问题
多线程同时修改同一数据,会产生竞态条件。
- 解决方案:使用
synchronized、Lock、AtomicXXX。
- 解决方案:使用
-
有序性问题
编译器指令重排导致结果异常。
- 解决方案:
volatile可以禁止指令重排。
- 解决方案:
-
缓存与数据库一致性问题
多线程同时修改数据,缓存与数据库不一致。
- 解决方案:使用分布式锁、消息队列或幂等机制。
四、分布式环境中多线程的问题
单体多线程可以共享同一内存,而分布式系统中,每个服务节点是独立进程,线程间无法直接通信。
常见问题:
-
跨节点数据一致性
- 多服务更新同一份数据;
- 数据写入顺序不同步。
- ✅ 解决方案:分布式锁(Redis/Zookeeper)、全局事务(Seata、TCC)。
-
线程上下文丢失
- Trace ID、用户信息、日志上下文在异步任务中丢失;
- ✅ 解决方案:使用
TransmittableThreadLocal或MDC传递上下文。
-
线程池过度创建
- 各个微服务都建线程池,导致系统总体线程数过多;
- ✅ 解决方案:统一线程池管理或异步消息化(MQ)。
-
无法共享内存状态
- 需要同步状态时,只能依赖外部中间件;
- ✅ 解决方案:用 Redis、Kafka 等共享状态。
五、分布式中替代多线程的方式
在分布式系统里,"多线程"不再是扩展性能的主要方式,取而代之的是更高级的异步与并行机制:
| 替代方案 | 说明 | 适用场景 |
|---|---|---|
| 消息队列 (MQ) | 异步削峰、解耦服务 | 下单→扣库存→发短信 |
| 异步事件驱动架构 (Event Bus, Kafka) | 服务之间通过事件传递,不需要共享内存 | 微服务事件通知 |
| 协程(Project Loom、Reactive编程) | 更轻量的并发模型,减少线程上下文切换 | 高并发 IO 服务 |
| 分布式任务调度系统 (XXL-Job、Quartz) | 定时、异步任务统一管理 | 批量任务调度 |
| 异步编排框架 (Workflow Engine) | 流程化执行多个服务任务 | 订单流程、审批流 |
六、总结
| 阶段 | 并发模型 | 核心技术 | 典型问题 | 解决思路 |
|---|---|---|---|---|
| 单体 | 多线程 | Thread、Executor、@Async | 资源竞争、数据一致性 | 锁、线程池、原子类 |
| 分布式 | 异步化 + 事件驱动 | MQ、Kafka、协程、分布式锁 | 跨节点一致性、上下文丢失 | 分布式事务、Trace传递、幂等机制 |
七、结语
多线程是性能的钥匙,但在复杂系统中也是最锋利的刀。
掌握它的原理、边界和替代方案,才能写出真正稳定、高并发的系统。