java面试场景题2中指令重排的解决方案

static int a=1,b=2,c=3;
int calc(){
    int r1 = a * b;
    int r2 = b * c;
    return r1 + r2;
}

先分清核心:这段代码的重排有没有危害、需不需要修复

1. 单线程场景:完全无害,不用处理

as-if-serial 规则:单线程内无论 CPU / 编译器怎么重排 r1=a*br2=b*c 的执行顺序,最终 r1+r2 结果一定不变,业务无 bug。

2. 多线程并发场景才会出问题

存在多线程同时修改 a/b/c + 调用 calc() 读取的情况下,重排会导致读取到不同时刻的变量快照,计算结果错乱:示例时序:

  1. 线程 1 执行 r1 = a*b(读取此时 a=1,b=2)
  2. 线程 2 修改 b=100
  3. CPU 重排先执行 r2 = b*c,读到新的 b=100,c=3
  4. 最终 r1=2、r2=300,混合新旧数据,结果不符合预期。

四种解决方案(按推荐优先级排序)

方案 1:a/b/c 全部加 volatile(简单通用,无锁)

volatile 变量读写自带内存屏障,禁止跨变量读写重排,保证每次读取都是主存最新值。

// 全部共享变量标记volatile
static volatile int a=1,b=2,c=3;

int calc(){
    int r1 = a * b;
    int r2 = b * c;
    return r1 + r2;
}

优缺点

✅ 改动最小,无线程阻塞;❌ 每次读写都触发内存屏障,高并发大量计算有轻微性能损耗;适用:读多写少、并发竞争不激烈场景。

方案 2:同步锁 synchronized 包裹整个计算(强一致性,读写互斥)

临界区保证:同一时间只有一个线程执行读取计算,且锁的内存屏障禁止临界区内、外指令互相重排,读取期间变量不会被修改。

static int a=1,b=2,c=3;

// 方法加锁
synchronized int calc(){
    int r1 = a * b;
    int r2 = b * c;
    return r1 + r2;
}

如果写变量的方法也加同一把锁,实现读写互斥,彻底杜绝混合新旧值问题。

优缺点

✅ 一致性最强,不会读到中间修改状态;❌ 并发会阻塞线程,大量并发计算吞吐量低;适用:读写竞争激烈、必须保证数据快照统一。

方案 3:先一次性拷贝所有共享变量到局部变量,再计算(性能最优,首选)

原理

先把 a/b/c 一次性读进线程私有局部变量,后续计算只操作局部变量,共享变量只读取一次,不存在中途被修改、重排打乱快照的问题。

static int a=1,b=2,c=3;

int calc(){
    // 第一步:统一拷贝共享变量到局部,只读取一次主存
    int va = a;
    int vb = b;
    int vc = c;
    // 后续计算全部基于局部变量,不受共享变量修改、指令重排影响
    int r1 = va * vb;
    int r2 = vb * vc;
    return r1 + r2;
}

核心优势

  1. 无 volatile、无锁,几乎无性能损耗;
  2. 无论 CPU 怎么重排内部乘法顺序,使用的都是同一瞬间的变量快照;
  3. 多线程修改全局变量不会干扰本次计算结果。生产中大量数值计算并发场景最优方案。

方案 4:使用 AtomicInteger 原子类封装 a/b/c(适合高频修改场景)

把普通 int 替换为原子类,get () 自带可见性,不会读到缓存过期值,规避重排带来的快照混乱:

static AtomicInteger a=new AtomicInteger(1);
static AtomicInteger b=new AtomicInteger(2);
static AtomicInteger c=new AtomicInteger(3);

int calc(){
    int va = a.get();
    int vb = b.get();
    int vc = c.get();
    int r1 = va * vb;
    int r2 = vb * vc;
    return r1 + r2;
}

适用场景

业务频繁并发修改 a/b/c,同时大量读取计算;原子类的 get 具备 volatile 读写语义。

错误方案避坑

只给单个变量加 volatile(比如只加 b)无法彻底解决:

static int a=1;
static volatile int b=2;
static int c=3;

a、c 无可见性保障,读取仍可能被重排、缓存到寄存器,依旧会混合新旧数据。

选型总结

  1. 追求性能、无锁优先 → 方案 3(局部变量拷贝快照)
  2. 代码极简、读多写少 → 方案 1(全部 volatile)
  3. 读写竞争激烈、需要强数据一致性 → 方案 2(synchronized 同步)
  4. 高频并发修改共享数值 → 方案 4 AtomicInteger

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值