CPU指令重排笔记

该博客通过一个Java示例代码展示了CPU指令重排可能导致的并发问题,强调了内存屏障在多线程环境中的作用,解释了如何使用内存屏障防止指令重排对程序执行结果的影响。内容涉及volatile关键字、线程同步和并发控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CPU指令重排实例代码:

public class VolatileSample2 {

    static volatile int a = 0, b = 0;
    static /*volatile*/ int x = 0, y = 0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for (; ; ) {
            i++;
            a = 0;
            b = 0;
            x = 0;
            y = 0;
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    shortWait(40000);
                    a = 1;
                    y = b;
                }
            });

            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    x = a;
                }
            });
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            String result = "第" + i + "次, (" + x + "," + y + ")";
            if (x == 0 && y == 0) {
                System.err.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }

    }

    // 一段延迟代码
    private static void shortWait(long interval) {
        long start = System.nanoTime();
        long end;
        do {
            end = System.nanoTime();
        } while (start + interval >= end);
    }
}

通过内存屏障可以防止指令重排对结果造成影响

内存屏障规则:

 

### Java 中指令重排的原因 在计算机体系结构中,为了提高处理器性能,现代 CPU 实现了乱序执行技术。这种技术允许 CPU 对无依赖关系的指令重新排列以优化资源利用率和减少等待时间[^4]。然而,在多线程环境下,这种行为可能导致某些特定条件下程序的行为不符合预期。 --- ### 指令重排的影响 当多个线程共享数据时,如果其中一个线程修改了一个变量而另一个线程读取该变量,则可能出现不一致的情况。这是因为编译器或运行时环境可能会对代码中的语句进行调整以提升效率,而这可能破坏程序员期望的操作顺序[^3]。具体来说: - **可见性问题**:即使一个线程已经更新了某个变量,其他线程也可能看不到最新的值。 - **有序性问题**:对于一些需要严格遵循发生次序的任务而言,指令被重新安排会打破原有的逻辑链条。 例如,在双重校验锁模式下创建单例对象时如果没有采取适当措施防止指令重拍现象的发生就容易造成实例化失败或者返回未完全初始化的对象给调用方[^2]。 --- ### 解决方案 #### 方法一:volatile 关键字 `volatile` 是一种轻量级同步机制,它能够确保标记的变量在每次访问前都从主存中获取最新版本而不是缓存副本,并且禁止对其所修饰字段相关的写入与后续读取之间的任何形式的重排序操作[^1]。 ```java private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); // 这里涉及三个步骤:分配内存空间 -> 调用构造函数初始化对象 -> 将对象指向刚分配的内存地址 } } } return instance; } ``` 上述例子展示了如何利用 `volatile` 来避免因指令重排而导致的问题。 #### 方法二:synchronized 锁定 虽然加锁开销较大,但它可以有效阻止同一时刻只有一个线程能进入临界区,因此自然也规避掉了由指令重排引起的风险。 #### 方法三:使用原子类(Atomic Classes) Java 提供了一些特殊的工具包如 java.util.concurrent.atomic 下的各种原子里类型,它们内部实现了基于 CAS(Compare And Swap) 的算法来完成高效并发控制而不必担心低层硬件层面可能发生的变化影响到高层应用逻辑。 --- ### 总结 综上所述,理解并妥善处理好 JVM 层面以及底层架构上的特性对于我们编写健壮可靠的软件至关重要。通过合理运用诸如 `volatile`, `synchronized` 或者 Atomic 类型等功能组件可以帮助我们克服由于指令重排所带来的挑战。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值