i++不是原子操作,因底层对应getstatic、iadd、putstatic三条字节码,多线程下可能交叉执行导致结果错误;应使用atomicinteger或synchronized保障原子性。

Java 里 i++ 不是原子操作,别当它是“一行就安全”
哪怕只写 i++ 这样一行 Java 代码,底层也会被编译成至少三条字节码:getstatic、iadd、putstatic。JVM 执行时可能在任意一步被线程抢占,导致两个线程同时读到旧值、各自加 1、再写回,结果只加了一次。
- 常见错误现象:
int count = 0;在多线程里反复执行count++,最终结果远小于预期次数 - 这不是 JVM Bug,是语言规范决定的:Java 只保证对
long和double以外的基本类型读写(getfield/putfield)是原子的,不保证复合操作 - 别依赖“看起来只有一行”,真正原子的是
AtomicInteger.incrementAndGet()或加synchronized
volatile 不能替代 synchronized 来保护 i++
volatile 只能保证变量的可见性和禁止重排序,它不提供执行期间的互斥。即使把 i 声明为 volatile int i,i++ 的读-改-写三步依然可能交叉执行。
- 典型误用场景:用
volatile boolean flag控制循环,再配合volatile int counter++统计次数——后者仍会丢数 -
volatile对++类操作无效,但对纯赋值(如flag = true)和简单状态切换是安全的 - 如果只是想避免锁开销,优先选
AtomicInteger,它的incrementAndGet()底层用的是 CAS 指令,在 x86 上对应lock xadd,硬件级原子
看字节码才能确认“哪一步不安全”
别猜,直接用 javap -c 看反编译结果。比如这段代码:
public class Counter {
static int i;
public static void inc() { i++; }
}
执行 javap -c Counter 会看到:
立即学习“Java免费学习笔记(深入)”;
public static void inc();
Code:
0: getstatic #2 // Field i:I
3: iconst_1
4: iadd
5: putstatic #2 // Field i:I
这四步中,第 0 步(读)、第 4 步(算)、第 5 步(写)之间都可能被中断。只要中间插入另一个线程的相同流程,就出错。
- 所有涉及“读原值→计算→写新值”的操作,包括
i += 2、list.add(x)(非线程安全集合)、map.get(k) == null ? map.put(k, v) : map.get(k),都属于这类 - Java 8+ 的
ConcurrentHashMap.computeIfAbsent()是例外,它内部做了原子保障,但得看文档确认,不能凭名字推断
不是所有“一行代码”都危险,但边界很窄
真正能放心当成原子操作的,只有极少数:基本类型纯赋值(i = 1)、引用赋值(obj = new Foo())、以及明确标注原子性的 API(如 AtomicReference.set())。
-
String s = "hello";安全,因为是引用赋值;但s += " world";不安全,它等价于s = new StringBuilder(s).append(" world").toString(),中间对象创建、方法调用、字段修改全是开放的 - 数组元素赋值
arr[0] = 1是原子的,但arr[0]++不是——前者是单条iastore,后者要先iaload再iastore - 最容易被忽略的是“看似无害的 getter/setter”:如果里面调用了非原子逻辑(比如缓存更新、日志记录),那一行调用就不等于原子操作










