Java Concurrency
Java 线程是并发编程的核心,允许程序同时执行多个任务。虚拟线程相关内容
线程概述
线程(Thread)是操作系统能够进行运算调度的最小单位。在 Java 中,线程由 JVM 管理为轻量级执行单元:多个线程共享同一进程的内存空间(堆、方法区),但各有独立的栈空间(局部变量、方法调用栈)。Java 线程基于操作系统原生线程实现,通过 java.lang.Thread 和 java.lang.Runnable 提供支持。
线程的创建
继承 Thread 类
继承 Thread 并重写 run()。简单但不推荐——Java 单继承,会限制类的扩展性。
1 | |
实现 Runnable 接口
更灵活、推荐——允许类继承其他类,且便于资源共享。
1 | |
Callable + Future
允许线程返回结果或抛出异常,通常与 ExecutorService 结合,适合需要返回值或异常处理的场景。
1 | |
线程池(Executor Framework)
java.util.concurrent 提供线程池机制,避免频繁创建/销毁线程的开销。
1 | |
Executors 快捷工厂及隐患
| 工厂方法 | 核心/最大线程 | 队列 | 隐患 |
|---|---|---|---|
newFixedThreadPool(n) | n / n | 无界 LinkedBlockingQueue | 任务无限堆积 → OOM |
newCachedThreadPool() | 0 / Integer.MAX_VALUE | SynchronousQueue | 突发流量创建大量线程 → 资源枯竭 |
newSingleThreadExecutor() | 1 / 1 | 无界 LinkedBlockingQueue | 任务堆积放大延迟 |
newScheduledThreadPool(n) | n / n | 延迟队列 | 长任务阻塞调度线程 |
生产建议:尽量自定义
ThreadPoolExecutor+ 有界队列 + 拒绝策略形成背压,而非依赖 Executors 快捷工厂。
自定义 ThreadPoolExecutor
1 | |
构造参数:
1 | |
- corePoolSize:基线线程数。低于此值时优先创建新线程;
allowCoreThreadTimeOut(true)后核心线程也会空闲退出。 - maximumPoolSize:线程上限。队列无法接收且有界时才扩容到此值。注意:用无界队列时 maximumPoolSize 实际无效。
- keepAliveTime + unit:非核心线程空闲存活时间;开启
allowCoreThreadTimeOut后也作用于核心线程。 - workQueue:决定伸缩与拒绝行为的关键。
SynchronousQueue:零容量直接移交,易扩到 maximumPoolSize,适合短任务+高并发但需强限流。ArrayBlockingQueue(cap):有界,配合 CallerRunsPolicy 形成自然背压。LinkedBlockingQueue(cap):传 cap 为有界;不传近似无界(不建议生产)。PriorityBlockingQueue:按优先级出队,任务需实现 Comparable。
- threadFactory:自定义线程名、优先级、守护属性与 UncaughtExceptionHandler,便于观测排障。
- handler:触发于"线程达 max 且队列满"或执行器已 shutdown。
AbortPolicy(默认,抛异常)、CallerRunsPolicy(背压)、DiscardPolicy(丢弃)、DiscardOldestPolicy(丢最老);可自定义告警/降级。
任务接纳流程:运行线程数 < core → 新建线程 → 否则入队 → 入队失败且 < max → 新建线程 → 否则触发拒绝策略。
拒绝策略选择:AbortPolicy(显式失败)、CallerRunsPolicy(背压)、DiscardPolicy、DiscardOldestPolicy。
线程数参考:CPU 密集 ≈ 核心数;IO 密集可高于核心数,但需观测上下文切换与资源句柄。
常用调优/监控方法:allowCoreThreadTimeOut(true)、prestartAllCoreThreads()、setRejectedExecutionHandler(...)、getPoolSize()/getActiveCount()/getQueue().size()/getCompletedTaskCount()。
优雅关闭
1 | |
在容器/服务停止钩子中调用优雅关闭,避免任务"半途而废"。监控接入 Micrometer/JMX,结合告警阈值动态调参。
ScheduledExecutor 定时语义
scheduleAtFixedRate(initialDelay, period, unit):固定频率,任务超 period 时按最小间隔串行继续。scheduleWithFixedDelay(initialDelay, delay, unit):从上次任务完成时刻起算下一次。
1 | |
定时任务应短小无阻塞;长任务用独立线程池或拆分。
虚拟线程
Executors.newVirtualThreadPerTaskExecutor() 适合大量阻塞型任务(如阻塞 IO),以低成本挂起/恢复。
1 | |
- CPU 密集任务不会因虚拟线程增多而更快。
- 避免长期持有同步器/本地代码调用造成 carrier pinning。
- 仍需背压策略与并发上限(如信号量)防止外部系统被打爆。
线程的生命周期
Java 线程有六种状态(定义在 Thread.State 枚举):
- NEW:对象创建但未调用
start()。 - RUNNABLE:已调用
start(),可能运行中或等待 CPU 调度。 - BLOCKED:等待获取锁(如
synchronized块)。 - WAITING:通过
wait()、join()或LockSupport.park()进入无限期等待。 - TIMED_WAITING:通过
sleep(long)、wait(long)或join(long)进入有限期等待。 - TERMINATED:执行完成或异常终止。
1 | |

线程的常用方法
start():启动线程,调用run()。run():线程执行逻辑,需重写。sleep(long millis):休眠指定时间,不释放锁。yield():让出 CPU,线程仍为 RUNNABLE。join():等待线程执行完成。interrupt():中断线程(设置中断标志,可能抛InterruptedException)。isAlive():检查线程是否存活。setPriority(int):设置优先级(1-10,默认 5)。setDaemon(boolean):设为守护线程(随主线程结束而结束)。
线程同步
多线程访问共享资源可能导致数据不一致,Java 提供多种同步机制。
synchronized 关键字
基于对象监视器(Monitor)实现互斥与可见性。每个对象天生携带一个监视器,与对象头 Mark Word 关联。JVM 根据竞争态在锁形态间转换(无锁 → 轻量级自旋 → 重量级阻塞);JDK 15 起移除了偏向锁。
- 字节码:同步块编译为
monitorenter/monitorexit指令对;同步方法通过ACC_SYNCHRONIZED标志由 JVM 隐式进入/退出。 - 两个队列:
EntryList(竞争监视器的线程) 与WaitSet(调用 wait 后挂起的线程);notify/notifyAll 仅将线程从 WaitSet 迁回 EntryList,恢复运行仍需重新竞争监视器。 - 可重入:同一线程可多次进入同一监视器,JVM 维护持有计数,退出时成对递减。
- 内存语义:退出监视器=发布(release),进入监视器=获取(acquire),形成 happens-before——随后获取的线程能看到临界区内最新写入。
- 中断语义:竞争进入监视器的阻塞不可被中断;
wait()可被中断并抛InterruptedException。 - 优化:自旋+阻塞混合策略降低内核态切换;JIT 可能锁粗化与锁消除。
锁粒度:
1 | |
不同实例各有监视器互不干扰;类锁在整个 JVM 中对该类唯一。
可重入示例:
1 | |
正确使用 wait/notify
wait/notify/notifyAll 必须在持有同一监视器的 synchronized 块内调用,并使用 while 循环检查条件以防虚假唤醒(spurious wakeup)与丢信号:
1 | |
notify只唤醒一个,可能"唤错对象";复杂场景优先notifyAll或拆分多个 Condition。- 避免"先通知后等待"丢信号:用共享条件变量 + while 检查。
- 中断处理:
wait()抛InterruptedException后应恢复中断标志或按业务退出。
锁对象选择与封装
锁对象应为 private final,避免锁定 this、Class 对象或字符串常量(会被驻留,可能与他处冲突),防止外部代码干扰。
1 | |
同步集合的迭代规约
Collections.synchronizedXxx 遍历时仍需外部同步(在同一监视器上),否则结构性变化不安全:
1 | |
安全发布与可见性
安全发布(Safe Publication)方式:静态初始化/不可变对象、volatile 字段、synchronized 的发布与访问。
- 何时选
volatile:变量独立、无复合不变式、只需可见性与有序性、不需要原子复合操作。 - 何时选
synchronized:存在复合操作或不变式,需互斥+可见性同时保证。
性能、死锁与取舍
- 最小化临界区,避免其中执行 I/O、RPC、睡眠。
- 死锁规避:多锁按统一顺序获取,跨组件制定顺序协议;保持临界区短小、减少嵌套。
synchronized不支持超时/可中断获取,需转向Lock.tryLock(...)。- 优先块级
synchronized(仅包裹必要共享状态),避免方法级锁过度串行化。 - 高并发吞吐需求优先选择高层并发结构(
ConcurrentHashMap、BlockingQueue)。
Lock 接口(ReentrantLock / ReadWrite / Stamped)
java.util.concurrent.locks.Lock 比 synchronized 更丰富:可中断加锁、超时获取、公平性、多条件队列、多种锁形态。最常用 ReentrantLock。必须用 try/finally 释放。
1 | |
- 非阻塞尝试:
lock.tryLock()立即返回,不可用走降级/排队/丢弃。 - 可中断:
lock.lockInterruptibly()响应中断,避免无限等待。 - 超时获取:
lock.tryLock(200, TimeUnit.MILLISECONDS)限时内未获取则放弃。 - 公平锁:
new ReentrantLock(true)按等待顺序获取,避免饥饿(吞吐略降)。 - 状态查询(仅监控/调试,勿做强逻辑):
isLocked()、isHeldByCurrentThread()、getHoldCount()、hasQueuedThreads()、getQueueLength()。
死锁规避(超时 + 退避重试):
1 | |
与 synchronized 差异:互斥/可见性等价;Lock 支持公平、可中断、超时、多条件队列;需手动释放(必须 try/finally),错误释放更易出错。
ReentrantReadWriteLock(读写锁)
读多写少场景提升吞吐:多读并行,写独占。不支持升级(读→写易死锁,需释放读锁再尝试写锁)。
1 | |
StampedLock(乐观读)
相较读写锁提供乐观读以提升读性能;不重入,且不支持 lockInterruptibly,谨慎使用。严禁同线程重复加同类锁,否则死锁。
1 | |
volatile 关键字
确保变量的可见性、防止指令重排序,但不保证原子性。典型用于双重检查单例:
1 | |
原子类(AtomicXxx)
原子类提供无锁(非阻塞)的原子性读改写能力,底层依赖硬件 CAS 指令与内存屏障,用于计数器、状态位切换、引用替换等场景。
CAS 与内存语义
CAS(Compare-And-Swap):期望值等于当前值时原子更新,返回是否成功。高并发下常配合自旋重试,避免线程阻塞/唤醒开销。注意 ABA 问题(A→B→A 往返无法被单纯 CAS 发现),需带戳/带标记引用类规避。
1 | |
方法语义:get()/set() 等价 volatile 读/写;lazySet() 更弱发布(最终一致,低开销);compareAndSet 成功时全栅栏;getAndSet/getAndAdd/incrementAndGet 等为读改写复合原子操作;getAndUpdate(fn)/accumulateAndGet(x, acc) 用函数式更新避免手写自旋。
常见原子类族
| 类族 | 适用场景 |
|---|---|
AtomicBoolean/Integer/Long | 计数器、开关、序列号等单变量原子更新 |
LongAdder/DoubleAdder | 高争用累加,分段分箱(Striped64)降热点,远优于 AtomicLong |
LongAccumulator/DoubleAccumulator | 自定义聚合函数的累加器 |
AtomicReference<T> | 原子替换引用,常与不可变对象搭配避免"半更新" |
AtomicStampedReference/AtomicMarkableReference | 带戳/带标记,抗 ABA |
AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray | 数组下标原子操作 |
AtomicIntegerFieldUpdater/LongFieldUpdater/ReferenceFieldUpdater | 反射驱动字段更新器,字段须为 volatile 非 final |
1 | |
选型与注意
- 计数选型:单核/低争用 →
AtomicLong;多核/高争用 →LongAdder;需精确线性化读快照 →AtomicLong。 - 不要将"原子类 + 普通字段"混用形成跨变量不变式;遇到不变式引入锁或更高层抽象。
- 跨多变量事务性要求(如"余额+库存"同时扣减)用锁/STM/数据库事务;阻塞等待场景用 Lock+Condition 或 Semaphore/CountDownLatch/Phaser。
线程安全集合
并发场景优先选内置并发集合而非手写锁,三大方案:
- 同步包装器(阻塞):
Collections.synchronizedXxx - 无锁/细粒度锁并发集合:
ConcurrentHashMap、ConcurrentLinkedQueue/Deque、ConcurrentSkipListMap/Set - 阻塞/可转移队列:
BlockingQueue、TransferQueue
同步包装器
Collections.synchronizedList(new ArrayList<>()) 等对每次访问用同一把内置锁,简单但高并发吞吐低,遍历需外部加锁。适合并发度不高、旧代码快速加固。
CopyOnWriteArrayList / CopyOnWriteArraySet
写时复制:每次写操作复制底层数组,写代价高、读无需加锁且遍历为快照。适合读多写少、元素少的场景(监听器/订阅者列表、白名单)。迭代器不抛 CME 且不反映迭代期间后续写入,但不支持 remove()。不适合大集合或高写频。
ConcurrentLinkedQueue / Deque
无界、无锁(基于 CAS)的多生产者多消费者队列,操作非阻塞,适合高吞吐"邮箱/缓冲"。迭代弱一致不抛 CME;size() 为 O(n) 近似值,避免热路径调用。
BlockingQueue / TransferQueue
阻塞 put/take 与超时 offer/poll,适合生产者-消费者。常见实现:
| 实现 | 特点 |
|---|---|
ArrayBlockingQueue | 有界数组环形队列,内存本地性好,支持公平 |
LinkedBlockingQueue | 链表,容量可选(默认近似无界),吞吐稳定 |
SynchronousQueue | 容量 0 直接移交,生产消费必须配对(Cached 线程池) |
PriorityBlockingQueue | 基于堆的无界优先队列 |
DelayQueue | 元素实现 Delayed,到期后可出队 |
LinkedTransferQueue | 支持 transfer 语义,有消费者时直接交付 |
1 | |
ConcurrentHashMap / ConcurrentSkipListMap
ConcurrentHashMap(无序):禁止 null key/value;迭代弱一致不抛 CME;size() 仅近似,可用 mappingCount()。
单 key 原子复合操作用 putIfAbsent/compute/computeIfAbsent/merge/replace(key,old,new) 替代"get-then-put"竞态。映射函数应短小无阻塞,勿在其中做结构性修改。批量并行操作 forEach/reduce*/search* 阈值决定是否并行。
1 | |
ConcurrentSkipListMap/Set(有序/可导航):基于跳表,提供 subMap/headMap/tailMap、ceiling/floor/higher/lower 等导航能力。读多写少且需排序/范围查询时优势明显,代价是写入更慢、内存开销更高。
选型对照
| 需求 | 推荐 |
|---|---|
| 列表读多写少、订阅/监听器 | CopyOnWriteArrayList |
| 高吞吐非阻塞邮箱/缓冲 | ConcurrentLinkedQueue/Deque |
| 生产者-消费者(有界平滑) | ArrayBlockingQueue |
| 生产者-消费者(吞吐弹性) | LinkedBlockingQueue |
| 直接交付/主动交付 | SynchronousQueue / LinkedTransferQueue |
| 并发字典(无序高吞吐) | ConcurrentHashMap |
| 并发字典(有序/范围查询) | ConcurrentSkipListMap |
| 旧容器快速加固 | Collections.synchronizedXxx(遍历需外部加锁) |
并发容器
size()常为近似或代价高,避免在热路径依赖 size 做控制逻辑。
线程通信
线程通信用于协调进度、传递数据与信号。正确原语既要保证可见性/有序性,也要提供合适的阻塞/唤醒/取消语义。
happens-before 要点:同一监视器释放 hb 随后获取;BlockingQueue put hb 相应 take;CountDownLatch 计数到 0 hb 所有 await 返回;CyclicBarrier/Phaser 阶段推进形成屏障;CompletableFuture 完成 hb 依赖阶段开始。多数阻塞方法可中断,抛 InterruptedException 后应退出或恢复中断标志。
Object.wait/notify
依托对象监视器,wait 释放监视器进入等待队列,被唤醒后需重新竞争。模式见上文synchronized 的 wait/notify。带超时等待可用 wait(timeoutMs) 配合时间预算循环。不适合复杂条件编排与精细中断/超时控制,转用 Condition。
LockSupport.park/unpark
单个"许可"语义:unpark 可先发生,持有许可时下一次 park 立即返回,不累加多许可。无需持有锁,常用于构建更高层同步器。仅传递"继续执行"信号,不附带条件检查与可见性保障,需与共享状态(volatile/锁)配合。
1 | |
Lock + Condition
一个 Lock 可派生多个 Condition,分离不同等待理由、减少无关唤醒,支持 awaitNanos/awaitUntil 带时钟等待。
1 | |
单条件、等待者等价时可用 signal;多条件/复杂依赖优先 signalAll 或拆分 Condition。
阻塞队列(哨兵停机)
通过 put/take 安全交接,天然具备可见性与可中断阻塞。哨兵(毒丸)模式优雅停机:
1 | |
CountDownLatch / CyclicBarrier / Phaser
| 同步器 | 特点 |
|---|---|
CountDownLatch | 一次性闩,主线程等待若干事件完成(计数到 0),不可重置 |
CyclicBarrier | 固定参与者,每轮全员到屏障同时推进,可附加 barrierAction,可复用 |
Phaser | 参与者可动态注册/撤销,适合变更规模的多阶段任务 |
1 | |
Semaphore(信号量/限流)
通过许可数限制并发度,用于有限资源访问或令牌桶限流。
1 | |
Exchanger(配对交换)
两个线程在交换点互换对象,适用于"双缓存"生产-消费。
CompletableFuture(异步编排)
阶段式回调链传递结果与信号,支持 anyOf/allOf、超时、取消与异常传播。
1 | |
CPU 密集尽量用专用池;避免在 thenApply 等同步阶段执行阻塞操作,必要时用 thenApplyAsync/thenComposeAsync。
通信模式选型
| 场景 | 推荐 |
|---|---|
| 生产者-消费者(背压) | 有界 BlockingQueue / LinkedTransferQueue |
| 多阶段流水线 | CyclicBarrier / Phaser / 多段队列串接 |
| 资源限流/并发控制 | Semaphore |
| 条件协调 | Lock + 多 Condition / wait-notifyAll |
| 异步请求/聚合 | CompletableFuture anyOf/allOf + 超时降级 |
| 自定义阻塞 | LockSupport.park/unpark + 明确共享状态与内存语义 |
常见陷阱:未持锁调用 wait/notify;if 而非 while 中 wait;notify 唤错等待者丢信号;compute/merge 内阻塞操作致串行化/死锁;忽略中断超时致线程泄漏;用近似 size 做严谨控制逻辑。
线程安全问题与解决
常见问题:数据竞争(多线程同时修改共享变量)、死锁(相互等待对方释放锁)、活锁(不断尝试但无法进展)、线程饥饿(无法获取 CPU 时间)。
解决思路:使用同步机制(synchronized/Lock);避免嵌套锁降死锁风险;用线程池管理线程;优先高层次并发工具(ConcurrentHashMap/ExecutorService)。
伪共享(False Sharing)
多核处理器中,多个线程频繁修改位于同一缓存行的不同变量时,即使变量逻辑独立,也会导致缓存行在 CPU 核心间频繁失效与同步,性能急剧下降(可能比单线程慢 10 倍+)。
原理与影响
- 缓存行通常 64 字节(x86),是 CPU 缓存与主存交换的最小单位。
- MESI 协议标记缓存行 M/E/S/I 状态;两个线程改同缓存行不同变量 → 缓存行反复失效重载(缓存行颠簸、总线争用)。
- 典型场景:高并发计数器、统计数组、并发队列、线程池工作窃取。
1 | |
解决方案
- 优先用成熟并发库:
LongAdder(内部 Cell 数组分片)、ConcurrentHashMap等已优化伪共享。 - 手动填充(Padding):加无用字段填充缓存行;可能被 JIT 消除,需谨慎。
- @Contended 注解(JDK 8+):官方填充机制,需
-XX:-RestrictContended启用,默认填充 128 字节(-XX:ContendedPaddingWidth)。仅用于 JDK 内部或性能关键路径,应用代码慎用。
1 | |
检测
1 | |
用 JMH 基准测试量化优化效果,并考虑不同 CPU 架构缓存行差异(64/128 字节)。
高级主题
Fork/Join 框架
用于分治算法,适合递归任务分解。
1 | |
ThreadLocal
为每个线程提供独立变量副本。注意用完 remove() 防止内存泄漏(尤其线程池场景)。
1 | |
最佳实践与调试
- 优先线程池,避免手动创建大量线程。
- 最小化锁范围,临界区不执行 I/O/RPC/睡眠。
- 避免共享状态,尽量用不可变对象或局部变量。
- 正确处理中断:捕获
InterruptedException并恢复中断状态。 - 测试并发代码:用 JMeter、JCStress 测试线程安全性。
调试:死锁用 jstack 或 IDE 查看线程状态;性能用 VisualVM/JProfiler 分析瓶颈;内存泄漏检查 ThreadLocal 是否清理。
总结
Java 通过 Thread/Runnable/Executor 提供并发支持。合理使用同步机制(synchronized/Lock/volatile)、线程池与并发工具,可有效管理多线程复杂性;Fork/Join、CompletableFuture 等进一步提升灵活性。核心原则:优先高层并发结构而非手写锁,最小化共享与临界区,统一中断与停机协议。






