【Java 并发编程】线程指令重排序问题 ( 指令重排序规范 | volatile 关键字禁止指令重排序 )

简介: 【Java 并发编程】线程指令重排序问题 ( 指令重排序规范 | volatile 关键字禁止指令重排序 )

文章目录

总结

一、指令重排序规范

二、指令重排序示例

总结

Java 并发的 3 33 特性 :


原子性 : 每个操作都是 不可拆分的原子操作 ; 在线程中进行 a++ 就不是原子操作 , 该操作分为 3 33 个步骤 , 首先从主内存中读取 a 变量 , 然后进行自增操作 , 最后在将自增后的值写回主内存中 ;

可见性 : 多个线程 访问同一个变量 , 该变量一旦被 某个线程修改 , 这些线程必须可以 立刻看到被修改的值 ;

有序性 : 程序按照 代码先后顺序 执行 ;

使用 volatile 关键字只能保证 可见性 和 有序性 , 但是不能保证原子性 ;


volatile 可以激活线程共享变量的 " 缓存一致性协议 " ; 保证 可见性 ;


volatile 可以 禁止 JVM 的 " 指令重排 " ; 保证 有序性 ;






一、指令重排序规范


指令重排指的是 , 线程中如果两行代码 没有逻辑上的上下关系 , 可以对代码进行 重新排序 ;



JVM 指令重排遵循规范 :


as-if-serial 规范 : 单个线程中, 指令的重排 , 不能影响程序的执行结果 ;


可以重排的情况 : 对于下面代码 , 两条指令顺序颠倒 , 执行结果相同 , 可以进行指令重排 ;

x = 0;
y = 1;


不可以进行重排的情况 : 对于下面的代码 , 两条指令如果上下颠倒 , 结果不同 , 不可以进行指令重排 ;

x = 0;
y = x;


happens-before 规范 : 先行发生原则 ;






二、指令重排序示例


指令重排示例 :


public class Main {
    // 使用 volatile 关键字修饰变量可以禁止指令重排
    /*volatile static int x = 0;
    volatile static int y = 0;
    volatile static int a = 0;
    volatile static int b = 0;*/
    // 没有使用 volatile 关键字修饰, 会产生指令重排的情况
    static int x = 0;
    static int y = 0;
    static int a = 0;
    static int b = 0;
    /**
     * 多线程运行导致异常值出现, 是由于指令重排导致的
     * @param args
     */
    public static void main(String[] args) {
        // 设置一个非常大的遍历数
        //      指令重排出现过程很少见, 基数越大, 出现概率越高
        for (int i = 0; i < Integer.MAX_VALUE; i ++) {
            // 每次循环都初始化变量
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            // 在该线程中, 如果出现指令重排
            //      先执行 b = 1, 在执行 x = a
            new Thread(new Runnable() {
                @Override
                public void run() {
                    x = a;
                    b = 1;
                }
            }).start();
            // 如果出现指令重排
            //      先执行 a = 1, 在执行 y = b
            new Thread(new Runnable() {
                @Override
                public void run() {
                    y = b;
                    a = 1;
                }
            }).start();
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            /*
                执行上述代码, 不是线程 1 先执行就是线程 2 先执行
                    如果线程 1 先执行, 则 x = 0, y = 1
                    如果线程 2 先执行, 则 x = 1, y = 0
                不可能出现 x = 1, y = 1 的情况
                    如果出现了, 则说明线程内部的执行顺序可能被颠倒了
                    出现了指令重排的情况
             */
            // 检查是否有异常值出现, 如果出现异常值, 退出方法
            if (x == 1 && y == 1) {
                System.out.println("出现异常值 x = 1, y = 1");
                return;
            } /*else {
                System.out.println("正常值 x = " + x + ", y = " + y);
            }*/
        }
    }
}


执行结果 :

image.png




两个线程的线程调度 :


协同式调度 : 线程执行时间 由 线程 决定 ;

抢占式调度 : 线程执行事件 由 系统 决定 ;

上述示例中的线程调度方式是 " 抢占式调度 " , 谁先执行 由系统分配 , 这两个线程的执行顺序都是随机的 , 可能线程 1 先执行 , 也可能是线程 2 先执行 ;


如果线程 1 先执行, 则 x = 0, y = 1 ;

如果线程 2 先执行, 则 x = 1, y = 0 ;


根据代码分析 , 不可能出现 x = 1, y = 1 的情况 , 如果出现了, 则说明 线程内部的执行顺序可能被颠倒了 , 出现了指令重排的情况 ;


目录
相关文章
|
7天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
21 2
|
10天前
|
Java
死磕-java并发编程技术(二)
死磕-java并发编程技术(二)
|
10天前
|
存储 Java 调度
死磕-java并发编程技术(一)
死磕-java并发编程技术(一)
|
10天前
|
设计模式 缓存 Java
死磕-高效的Java编程(一)
死磕-高效的Java编程(一)
|
13天前
|
算法 Java
JAVA并发编程系列(8)CountDownLatch核心原理
面试中的编程题目“模拟拼团”,我们通过使用CountDownLatch来实现多线程条件下的拼团逻辑。此外,深入解析了CountDownLatch的核心原理及其内部实现机制,特别是`await()`方法的具体工作流程。通过详细分析源码与内部结构,帮助读者更好地理解并发编程的关键概念。
|
11天前
|
存储 Java
Java编程中的对象和类
【8月更文挑战第55天】在Java的世界中,“对象”与“类”是构建一切的基础。就像乐高积木一样,类定义了形状和结构,而对象则是根据这些设计拼装出来的具体作品。本篇文章将通过一个简单的例子,展示如何从零开始创建一个类,并利用它来制作我们的第一个Java对象。准备好让你的编程之旅起飞了吗?让我们一起来探索这个神奇的过程!
25 10
|
12天前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。
|
12天前
|
Java
JAVA并发编程系列(9)CyclicBarrier循环屏障原理分析
本文介绍了拼多多面试中的模拟拼团问题,通过使用 `CyclicBarrier` 实现了多人拼团成功后提交订单并支付的功能。与之前的 `CountDownLatch` 方法不同,`CyclicBarrier` 能够确保所有线程到达屏障点后继续执行,并且屏障可重复使用。文章详细解析了 `CyclicBarrier` 的核心原理及使用方法,并通过代码示例展示了其工作流程。最后,文章还提供了 `CyclicBarrier` 的源码分析,帮助读者深入理解其实现机制。
|
12天前
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
23 11
|
9天前
|
Java 开发者
深入探索Java中的并发编程
本文将带你领略Java并发编程的奥秘,揭示其背后的原理与实践。通过深入浅出的解释和实例,我们将探讨Java内存模型、线程间通信以及常见并发工具的使用方法。无论是初学者还是有一定经验的开发者,都能从中获得启发和实用的技巧。让我们一起开启这场并发编程的奇妙之旅吧!
下一篇
无影云桌面