Java并发概述
Java 并发编程用于让多个任务在同一时间段内推进执行,从而提升吞吐、降低响应时间,并更好地利用多核 CPU 和外部 IO 等待时间。
并发不是“多开几个线程”这么简单。真正的并发能力包括:
- 任务如何拆分。
- 线程如何创建、复用和关闭。
- 共享数据如何保证安全。
- 线程之间如何通信。
- 内存可见性和指令重排序如何处理。
- 锁、原子类、并发容器、线程池如何选型。
# 1. 并发与并行
| 概念 | 含义 | 示例 |
|---|---|---|
| 并发 | 多个任务在同一时间段内交替推进 | 单核 CPU 切换执行多个线程 |
| 并行 | 多个任务在同一时刻真正同时执行 | 多核 CPU 同时运行多个线程 |
示意:
并发:
时间轴 -> A A B A C B C
并行:
CPU1 -> A A A A
CPU2 -> B B B B
CPU3 -> C C C C
Java 并发代码既可能运行在单核上的交替执行,也可能运行在多核上的真正并行。
# 2. 为什么需要并发
常见收益:
| 目标 | 说明 |
|---|---|
| 提升吞吐 | 多个任务同时处理,单位时间完成更多工作 |
| 降低延迟 | IO 等待时让其他任务继续执行 |
| 利用多核 | CPU 密集型任务可拆分并行计算 |
| 改善响应 | 后台任务异步执行,主流程快速返回 |
| 解耦流程 | 生产者和消费者通过队列协作 |
并发适合:
- Web 请求处理。
- 批量任务执行。
- 异步通知。
- 日志、消息、文件处理。
- 生产者消费者模型。
- 多核计算。
不适合为了“看起来高级”而滥用。并发会引入调度、同步、上下文切换和排查复杂度。
# 3. 并发问题地图
并发程序最常见问题可以归为三类:
并发问题
├─ 原子性
│ └─ 多步操作被线程切换打断
├─ 可见性
│ └─ 一个线程修改,另一个线程看不到
└─ 有序性
└─ 编译器/CPU/JIT 重排序影响执行观察结果
示例:
count++;
这不是一个原子操作,概念上包含:
读取 count
│
▼
加 1
│
▼
写回 count
多个线程同时执行时,可能发生更新丢失。
# 4. Java 并发体系
Java 并发
├─ 线程基础
│ ├─ Thread
│ ├─ Runnable
│ ├─ Callable
│ └─ 线程生命周期
├─ 内存模型
│ ├─ JMM
│ ├─ happens-before
│ └─ volatile
├─ 同步机制
│ ├─ synchronized
│ ├─ ReentrantLock
│ ├─ ReadWriteLock
│ └─ StampedLock
├─ 协作工具
│ ├─ wait/notify
│ ├─ CountDownLatch
│ ├─ CyclicBarrier
│ └─ Semaphore
├─ 执行框架
│ ├─ ThreadPoolExecutor
│ ├─ ScheduledThreadPoolExecutor
│ ├─ ForkJoinPool
│ └─ CompletableFuture
├─ 并发容器
│ ├─ ConcurrentHashMap
│ ├─ BlockingQueue
│ ├─ CopyOnWriteArrayList
│ └─ Atomic
└─ 排障与实践
├─ 死锁、活锁、饥饿
├─ 线程 dump 分析
├─ 线程池治理
└─ 生产最佳实践
学习顺序建议:
- 先理解线程、任务和生命周期。
- 再理解线程安全的三大问题。
- 接着学习
synchronized、volatile和 JMM。 - 然后学习线程池、锁、并发工具类。
- 最后学习并发容器、异步编排、虚拟线程、问题排查和最佳实践。
# 5. 并发不是越多线程越好
线程数量过多会带来成本:
- 上下文切换增加。
- 栈内存占用增加。
- 锁竞争加剧。
- CPU 缓存失效。
- 排查难度上升。
- 外部资源被打爆,例如数据库连接、HTTP 下游服务。
线程执行成本模型:
任务执行时间
= 真正工作时间
+ 等待时间
+ 调度时间
+ 同步竞争时间
+ 上下文切换成本
优化并发系统时,不能只调大线程数,要看瓶颈在哪里。
# 6. CPU 密集型与 IO 密集型
| 类型 | 特点 | 线程数倾向 |
|---|---|---|
| CPU 密集型 | 主要消耗 CPU,如计算、压缩、加密 | 接近 CPU 核心数 |
| IO 密集型 | 大量等待网络、磁盘、数据库 | 可多于 CPU 核心数 |
经验公式只能作为起点,最终要结合:
- CPU 使用率。
- 队列长度。
- 平均响应时间。
- P95/P99 延迟。
- 下游资源容量。
- GC 和内存情况。
# 7. 并发代码的工程原则
- 优先减少共享状态。
- 共享状态必须有清晰的同步策略。
- 不要手动创建大量裸线程,优先线程池。
- 线程池必须有边界:线程数、队列、拒绝策略。
- 锁范围尽量小,但不要为了小而破坏一致性。
- 不要在锁内做慢 IO。
- 明确任务取消、超时和异常处理。
- 并发问题必须靠压测、监控、日志和线程 dump 验证。
# Tips 快问快答
Q:并发和并行一样吗? A:不一样。并发强调同一时间段内多个任务推进,并行强调同一时刻真正同时执行。
Q:Java 并发最核心的问题是什么? A:原子性、可见性、有序性。
Q:线程越多性能越好吗? A:不是。线程过多会增加上下文切换、内存占用和资源竞争。
Q:为什么不建议直接 new Thread? A:不好统一管理生命周期、数量、异常和资源,生产代码通常用线程池。
Q:CPU 密集型任务线程数怎么估? A:通常接近 CPU 核心数,再通过压测调整。
Q:IO 密集型任务线程数能更多吗? A:可以,但必须受下游资源和队列容量限制。
Q:并发编程最好的优化是什么? A:减少共享状态和阻塞点,很多时候比加锁技巧更重要。
Q:并发 bug 为什么难排查? A:它依赖时序、调度和负载,常常无法稳定复现。
上次更新: 2026/06/25, 14:19:18