最后更新:2026-10-28 · 基于第 1 次作答
结论:i++非原子(读-改-写三步),多线程下保证正确本质是把"读-改-写"互斥或原子化——原子类/CAS、加锁、高并发用LongAdder;volatile只保可见性,单独用不够。 关键词:AtomicInteger · CAS 自旋 · LongAdder 分段 · volatile 边界 边界:只保单变量;高竞争换LongAdder;ABA 用版本号;复合操作加锁。
核心考点定位:考察 i++ 为何丢更新(读-改-写三条指令非原子),以及原子类/CAS、加锁、LongAdder 三种方案的边界差异;重点排除"volatile 能保证 i++ 原子"的误判,并追问高并发下的选型与 ABA 治理。
机制链(≥3 环):
i++ 编译后非单条指令 → 底层是"读值-加一-写回"三步:两个线程同时读到旧值 0,各自写回 1,i 只增加了一次,发生丢更新。synchronized/ReentrantLock 串行化临界区,或用 CAS 原子指令保证"比较-交换"一气呵成,每次 i 都精确 +1。cmpxchg 原子指令 → 高竞争下失败重试(自旋)空转烧 CPU:改用 LongAdder 分段 Cell 数组分散热点,或退化为锁。volatile 只保证可见性与有序性 → 没有互斥语义,修饰的 i 并发自增仍会丢更新:必须配合原子类或锁。证据锚点:
i++:非原子,读-改-写三条指令AtomicInteger:单变量原子自增incrementAndGet():原子 +1,返回新值getAndIncrement():原子 +1,返回旧值LongAdder:分段 Cell 累加,高并发更优sum():LongAdder 弱一致汇总AtomicStampedReference:ABA 版本号解法compareAndSwapInt:CAS 底层 APIcmpxchg:x86 原子比较-交换指令synchronized:互斥锁,串行化临界区volatile:只保证可见性与有序性Cell:LongAdder 内部分段单元对比与权衡:
| 方案 | 正确性 | 吞吐/成本 | 失败模式 | 适用边界 |
|---|---|---|---|---|
| AtomicInteger / CAS | 单变量原子 | 低竞争无锁、快 | 高竞争自旋烧 CPU、ABA | 单变量低竞争 |
| LongAdder | 原子但 sum 弱一致 | 高并发吞吐最高 | sum() 非强一致 | 高频计数、读多写少 |
| synchronized / Lock | 互斥、强一致 | 阻塞、上下文切换成本 | 死锁、停顿 | 复合操作、高竞争 |
| volatile | 不保证原子 | 低、内存屏障 | 并发自增仍丢更新 | 仅可见性标志、发布 |
常见误区:
volatile 只保证可见性与有序性,i++ 是复合操作,并发下仍会丢更新 → 为什么:volatile 没有互斥语义,"读-改-写"三条指令仍可被打断。LongAdder,分段 Cell 分散热点 → 为什么:全局 CAS 都打在同一个 value 上,LongAdder 分散到多个 Cell,竞争大幅下降。AtomicStampedReference 加版本号 → 为什么:值 A→B→A 后 CAS 会误判为"未被修改",隐蔽且危险。适用边界:
AtomicInteger);高频读多写少的计数(LongAdder);需要严格一致性的单变量(AtomicLong)。LongAdder 或退锁);复合操作(用 AtomicReference 打包或加锁);需要跨变量不变式(必须加锁)。AtomicStampedReference;复合操作用 ReentrantLock;只做标记用 volatile。i++ 是"读值-加一-写回"三条指令,非原子;两个线程读到同一旧值各写回,只加了一次Cell 数组分散热点,线程写不同 Cell 减少 CAS 竞争;sum() 是弱一致AtomicStampedReference 加版本号;AtomicMarkableReference 加布尔标记volatile 写屏障与 DCL 单例、状态发布场景【结论】 i++ 不是原子操作,多线程下保证正确的本质是把"读-改-写"整体互斥或原子化,三条主线:原子类/CAS、加锁、高并发用 LongAdder;volatile 只保可见性,单独修 i++ 不够。
【三点】
第一,原子类与 CAS:AtomicInteger.incrementAndGet() 靠 cmpxchg 原子完成"比较-交换",单变量低竞争最快;但高竞争时自旋空转烧 CPU,且存在 ABA 问题(用 AtomicStampedReference 版本号解决)。
第二,加锁:synchronized/ReentrantLock 串行化临界区,正确性最强,但有阻塞与上下文切换成本,适合复合操作或高竞争场景。
第三,LongAdder 与 volatile:高频读多写少的计数用 LongAdder(分段 Cell 分散热点,sum() 弱一致);volatile 只保证可见性与有序性,不保证原子,不能单独用来修 i++。
【边界】 三点提醒:一是只保单变量原子性,复合操作要 AtomicReference 打包或加锁;二是高竞争时 CAS 自旋反而更慢,换 LongAdder 或退化为锁;三是 volatile 不能替代互斥,只能做可见性标志。
题目:如何保证多线程下i++结果正确?
难度:medium
标签:java/concurrency-practice, java/concurrency, general/knowledge, difficulty/medium
评分:⭐⭐⭐☆☆(3/5)
多线程下保证i++正确可以有多种方案,i++命令并不是一个原子命令,如果只是要实现i+1的操作,可以直接用jdk的原子类内部自带加1等的操作,可以保证原子性的加减;另外可以使用CAS,通过比较交换来实现i++操作;最后是加锁,让所有线程串行化执行,来让i++在多线程下正确执行
评分:⭐⭐⭐☆☆(3/5)
优点:三方案覆盖全(原子类/CAS/加锁)、点出 i++ 非原子、有"串行化"意识。
不足:没讲 CAS 的 ABA 问题与自旋开销(高竞争下自旋浪费 CPU,可退化到锁或 LongAdder 分段累加);没讲 LongAdder 适合高频读多写少的计数场景(分段 Cell 数组 + 最后 sum());没讲 volatile 单独不能保证 i++ 原子(只能保证可见性)。
补充:①volatile 只保证可见性不保证复合原子性;②高竞争计数用 LongAdder(分段累加)比 AtomicLong 自旋更优;③CAS 注意 ABA(可用 AtomicStampedReference)。
完整讲解见上方「完整讲解(权威版)」