如何从 JVM 指令集分析中洞察 i 指令在字节码层面的非原子操作及潜在竞争问题?
- 内容介绍
- 文章标签
- 相关推荐
本文共计535个文字,预计阅读时间需要3分钟。
直接反编译包含以下方法的Java字节码:
static i++ 和 instance i++ 的字节码差异不影响原子性结论
静态变量用 getstatic/putstatic,实例变量用 getfield/putfield,但无论哪种,都逃不开“读→算→写”三阶段。区别只在内存寻址路径(类元数据区 vs 对象头),不改变操作可分割的本质。哪怕你把 i 声明为 volatile int i,getfield 和 putfield 能保证可见性与禁止重排序,但中间的 iadd 仍在线程私有栈上独立执行,无法阻止两个线程同时读到同一个旧值。
别信“单行代码=安全”,真正能当原子用的只有明确标注的 API
-
i = 1是原子的(基本类型纯赋值) -
obj = new Foo()是原子的(引用赋值) -
AtomicInteger.incrementAndGet()是原子的(底层 lock xadd 或 cmpxchg) -
i++、i += 2、list.add(x)、map.get(k) == null ? map.put(k, v) : map.get(k)全都不行——只要涉及“先读再算再写”,就踩进非原子雷区
验证竞态最简单的方式:写个 10 线程各跑 1000 次的测试
不用复杂框架,几行代码就能复现问题:
static int i = 0; public static void main(String[] args) throws InterruptedException { List<Thread> threads = new ArrayList<>(); for (int t = 0; t < 10; t++) { threads.add(new Thread(() -> { for (int j = 0; j < 1000; j++) i++; })); } threads.forEach(Thread::start); threads.forEach(t -> { try { t.join(); } catch (Exception e) {} }); System.out.println(i); // 多数情况下远小于 10000 }
这个结果不稳定本身,就是非原子性的铁证。想靠 sleep、yield 或 volatile 来“修复”它,只会掩盖问题,不会消除隐患。
真正难的不是看懂字节码,而是习惯性质疑每一处“读-改-写”组合——哪怕它只占一行、看起来毫无威胁。
本文共计535个文字,预计阅读时间需要3分钟。
直接反编译包含以下方法的Java字节码:
static i++ 和 instance i++ 的字节码差异不影响原子性结论
静态变量用 getstatic/putstatic,实例变量用 getfield/putfield,但无论哪种,都逃不开“读→算→写”三阶段。区别只在内存寻址路径(类元数据区 vs 对象头),不改变操作可分割的本质。哪怕你把 i 声明为 volatile int i,getfield 和 putfield 能保证可见性与禁止重排序,但中间的 iadd 仍在线程私有栈上独立执行,无法阻止两个线程同时读到同一个旧值。
别信“单行代码=安全”,真正能当原子用的只有明确标注的 API
-
i = 1是原子的(基本类型纯赋值) -
obj = new Foo()是原子的(引用赋值) -
AtomicInteger.incrementAndGet()是原子的(底层 lock xadd 或 cmpxchg) -
i++、i += 2、list.add(x)、map.get(k) == null ? map.put(k, v) : map.get(k)全都不行——只要涉及“先读再算再写”,就踩进非原子雷区
验证竞态最简单的方式:写个 10 线程各跑 1000 次的测试
不用复杂框架,几行代码就能复现问题:
static int i = 0; public static void main(String[] args) throws InterruptedException { List<Thread> threads = new ArrayList<>(); for (int t = 0; t < 10; t++) { threads.add(new Thread(() -> { for (int j = 0; j < 1000; j++) i++; })); } threads.forEach(Thread::start); threads.forEach(t -> { try { t.join(); } catch (Exception e) {} }); System.out.println(i); // 多数情况下远小于 10000 }
这个结果不稳定本身,就是非原子性的铁证。想靠 sleep、yield 或 volatile 来“修复”它,只会掩盖问题,不会消除隐患。
真正难的不是看懂字节码,而是习惯性质疑每一处“读-改-写”组合——哪怕它只占一行、看起来毫无威胁。

