volatile指令重排[通俗易懂]

volatile指令重排[通俗易懂]什么时候会发生指令重排?先来一个测试指令重排现象,下面这段代码会发生指令重排,也就是JVM优化了执行顺序。/***指令重排测试*/publicclassCommandDisorder{//当使用volatile关键词修饰变量时,则不会出现指令重排现象privatestatic/*volatile*/inta=0,b=0,c=0,d=0;/***测试方式:一次开启两个线程,同时修改变量*/

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

什么时候会发生指令重排?

先来一个测试指令重排现象,下面这段代码会发生指令重排,也就是JVM优化了执行顺序。

/** * 指令重排测试 */
public class CommandDisorder { 
   
    // 当使用volatile关键词修饰变量时,则不会出现指令重排现象
    private static /*volatile*/ int a = 0, b = 0, c = 0, d = 0;

    /** * 测试方式:一次开启两个线程,同时修改变量 */
    public static void main(String[] args) throws InterruptedException { 
   
        int i = 0;
        while (true) { 
   
            i++;
            a = b = c = d = 0;
            Thread t1 = new Thread(() -> { 
   
                a = 1;
                c = b; // 指令重排,会先执行这行代码,导致c = 0, d = 0
            });
            Thread t2 = new Thread(() -> { 
   
                b = 1;
                d = a; // 指令重排,会先执行这行代码,导致c = 0, d = 0
            });
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            if (c == 0 && d == 0) { 
   
                System.err.println(String.format("第%s次出现指令重排", i));
                break;
            } else { 
   
                System.out.println(i);
            }
        }
    }
}

指令重排,异常出现了:

img

什么是指令重排?

为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序,JMM内部会有指令重排,并且会有af-if-serial和happen-before的理念来保证指令的正确性。

  • af-if-serial:不管怎么重排序,单线程下的执行结果不能被改变;
  • 先行发生原则(happen-before):先行发生原则有很多,其中程序次序原则,在一个线程内,按照程序书写的顺序执行,书写在前面的操作先行发生于书写在后面的操作,准确地讲是控制流顺序而不是代码顺序。

真实的业务中如何避免指令重排?

在真实业务场景中,预测到可能有多线程访问同一个变量时,建议加上volatile关键词,保证变量在线程间的可见性。

举一个简单的例子,单例模式

public class Singleton { 
   
    // 为了避免指令重排,这里需要加上volatile关键词
    private static /*volatile*/ Singleton singleton = null;

    /** * double check lock(DCL) */
    public static Singleton getInstance() { 
   
        if (singleton == null) { 
   
            synchronized (Singleton.class) { 
   
                if (singleton == null) { 
   
                    // new 一个对象的过程,有三个步骤
                    // 1.内存分配
                    // 2.初始化
                    // 3.返回对象引用
                    // 由于JVM指令重排优化,可能会使得2、3两步顺序发生变化,说明这里不是一个原子性操作
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    private static void removeInstance() { 
   
        singleton = null;
    }

    private final String field = "init";

    public static void main(String[] args) throws InterruptedException, ExecutionException { 
   
        ExecutorService threadPool = Executors.newCachedThreadPool();

        long start = System.currentTimeMillis();
        for (int j = 0; j < 3000_000; j++) { 
   
            execute(threadPool);
            Singleton.removeInstance();
        }

        System.out.println("正常结束");
        System.out.println("执行耗时:" + (System.currentTimeMillis() - start) + " ms");
        System.exit(0);
    }

    /** * 使用500个线程同时去获取实例 */
    private static final int THREAD_COUNT = 500;
    private static void execute(ExecutorService threadPool) throws InterruptedException, ExecutionException { 
   
        CountDownLatch downLatch = new CountDownLatch(THREAD_COUNT);
        List<Callable<Singleton>> list = new ArrayList<>();
        for (int i = 0; i < THREAD_COUNT; i++) { 
   
            list.add(() -> { 
   
                downLatch.countDown();
                Singleton instance = Singleton.getInstance();
                if (instance.field == null) { 
   
                    throw new RuntimeException("获取到未实例化的对象");
                }
                return instance;
            });
        }
        List<Future<Singleton>> futures = threadPool.invokeAll(list);
        Set<Singleton> set = new HashSet<>();
        for (Future<Singleton> future : futures) { 
   
            set.add(future.get());
        }
        if (set.size() > 1) { 
   
            System.out.println("产生多实例!");
            throw new RuntimeException("产生多实例!");
        }
    }
}

当没有采用DCL时,可能会产生多实例。采用了DCL而没有使用volatile关键词,则可能出现:获取到未实例化的对象,原理见第一个示例。

看似一个简单的单例,内部却隐含了不少有意思的内容。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/183443.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • java图书馆新地址_基于SSM的社区图书馆管理系统的设计与实现[通俗易懂]

    java图书馆新地址_基于SSM的社区图书馆管理系统的设计与实现[通俗易懂]好程序设计擅长JAVA(SSM,SSH,SPRINGBOOT)、PYTHON(DJANGO/FLASK)、THINKPHP、C#、安卓、微信小程序、MYSQL、SQLSERVER等,欢迎咨询在学习社区图书馆管理系统的设计与实现项目的时候,方便日后能及时查阅,在本平台中记录一下社区图书馆管理系统的设计与实现的开发流程。在学习时候的选用了SSM(MYECLIPSE),这个框架…

    2022年7月9日
    79
  • rowBounds_robocopy用法

    rowBounds_robocopy用法generator添加

    2022年9月22日
    0
  • layoutSubviews和drawRect

    layoutSubviews和drawRectLayoutSubviews    子类可以重写此方法,因为需要更精确执行他们子视图的布局。只有当autoresizing和基于约束的行为的子视图不提供你想要的行为,应重写此方法。该方法不应直接调用。如果你想要强制布局更新,下一次绘图更新之前 应当反向调用setNeedsLayout方法,如果您想要立即更新您的视图的布局,请调用layoutIfNeeded方法。

    2022年7月15日
    14
  • c++中条件运算符_单目运算符有哪些

    c++中条件运算符_单目运算符有哪些条件运算符是C++中唯一一个三元运算符,要求有三个操作对象,条件表达式的一般形式为:表达式1?表达式2:表达式3条件运算符的执行顺序是,先求解表达式1,若为真则求解表达式2,此时表达式2的值作为整个条件表达式的值。若表达式1的值为假,则求解表达式3,表达式3的值为整个条件表达式的值。max=(a>b)?a:ba比b大时,关系表达式为真,条件表达式的值为a;b比a大时,关系表达式为假,条件

    2022年10月2日
    0
  • 目标检测的目的_小目标检测问题

    目标检测的目的_小目标检测问题我们在评价一个目标检测算法的“好坏”程度的时候,往往采用的是pascalvoc2012的评价标准mAP。网上一些资料博客参差不齐,缺乏直观易懂的正确说明。希望这篇博文能够给大家一点帮助。mAP历史目标检测的mAP计算方式在2010年的voc上发生过变化,目前基本都是采用新的mAP评价标准。(我有个小疑问就是明明是2010年修改的,但是貌似现在大家都称这种计算方式为2012)所…

    2022年10月12日
    0
  • 数学四大思想八大方法_四种思想方法,让你轻松掌握高中数学

    学习一门知识,究其核心,主要是学其思想和方法,这是学习的精髓。学数学亦如此,分学数学思想和数学方法。数学思想是指客观世界的空间形式和数量关系反映到人们的意识之中,经过思维活动而产生的结果。数学思想是对数学事实与理论经过概括后产生的本质认识。数学方法是指用数学语言表述事物的状态、关系和过程,并加以推导、演算和分析,以形成对问题的解释、判断和预言的方法。高中数学的四种思想方法:1.函数与方程思想1.1…

    2022年4月8日
    148

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号