java指令重排序举例,单线程会出现指令重排序

  java指令重排序举例,单线程会出现指令重排序

  一、前言二。问题恢复(I)相关变量1。结果预测2。指令重排(二)新创建对象1。解析创建过程2。重新排序过程分析。处理指令重排(一)AtomicReference原子类(二)volatile关键字1。指令重排很普遍2。多线程环境中的指令重排3。同步锁定和重新排序。

  00-1010指令重排有利于提高程序在单线程环境下的执行效率,不会对程序产生负面影响;在多线程环境中,指令重排会给程序带来意想不到的错误。

  本文对多线程指令重排问题进行了还原,并给出了相应的解决方案。

  

目录

 

  00-1010这里是一个可以100%恢复的指令重排的例子。

  ublic class D {静态整数a;静态布尔标志;公共静态void编写器(){ a=1;flag=true}公共静态void reader() { if (flag!=null flag){ system . out . println(a);a=0;flag=false}}

  只有当flag变量为true时,00-1010Reader方法才会将变量A的值打印到控制台。

  writer方法首先执行变量A的赋值,然后执行变量flag的赋值。

  按照上面的分析逻辑,控制台打印出来的结果肯定全是1。

  00-1010如果代码中没有发生指令重排,那么当标志变量为真时,变量A必须为1。

  在上面的代码中,两个方法类中都有变量A和变量flag的指令重排。

  公共静态void编写器(){ a=1;flag=true}通过观察日志输出,发现有大量的0输出。

  当指令重排发生在writer方法内部时,首先分配标志变量。此时,如果当前线程被中断,其他线程正在调用reader方法,并且检测到flag变量为真,则打印变量A的值。这时,控制台有了超出预期的结果。

  00-1010使用关键字new创建对象时,由于其非原子操作,存在指令重排,在多线程环境下会带来负面影响。

  公共类Singleton { private static user model实例;公共静态user model getInstance(){ if(instance==null){ synchronized(singleton . class){ if(instance==null){ instance=new user model(2, B );} } }返回实例;} } @ Data @ AllArgsConstructorclass user model { private Integer userId;私有字符串用户名;

  00-1010使用关键字new创建一个对象,大致可以分为以下几个过程:在栈空间创建一个引用地址,使用类文件作为模板,在栈空间为对象分配内存,初始化成员变量,使用构造函数初始化对左存储变量的引用值。

  00-1010对于上面的例子,假设第一个线程进入同步代码块,开始创建对象。因为重排序,正常的创建对象的过程被打乱了,可能会出现在堆栈空间中。

  用地址后,将引用值赋值给左侧存储变量,随后因CPU调度时间片耗尽而产生中断的情况。

  后续线程在检测到instance变量不为空,则直接使用。因为单例对象并为实例化完成,直接使用会带来意想不到的结果。

  

 

  

三、应对指令重排

 

  

(一)AtomicReference原子类

使用原子类将一组相关联的变量封装成一个对象,利用原子操作的特性,有效回避指令重排问题。

 

  

@Data@NoArgsConstructor@AllArgsConstructorpublic class ValueModel { private Integer value; private Boolean flag;}

原子类应该是解决多线程环境下指令重排的首选方案,不仅通俗易懂,而且线程间使用的非重量级互斥锁,效率相对较高。

 

  

public class E { private static final AtomicReference<ValueModel> ar = new AtomicReference<>(new ValueModel()); public static void writer() { ar.set(new ValueModel(1, true)); } public static void reader() { ValueModel valueModel = ar.get(); if (valueModel.getFlag() != null && valueModel.getFlag()) { System.out.println(valueModel.getValue()); ar.set(new ValueModel(0, false)); } }}

当一组相关联的变量发生指令重排时,使用原子操作类是比较优的解法。

 

  

 

  

(二)volatile关键字

public class Singleton { private volatile static UserModel instance; public static UserModel getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new UserModel(2, "B"); } } } return instance; }}@Data@AllArgsConstructorclass UserModel { private Integer userId; private String userName;

 

  

四、指令重排的理解

 

  

1、指令重排广泛存在

指令重排不仅限于Java程序,实际上各种编译器均有指令重排的操作,从软件到CPU硬件都有。指令重排是对单线程执行的程序的一种性能优化,需要明确的是,指令重排在单线程环境下,不会改变顺序程序执行的预期结果。

 

  

 

  

2、多线程环境指令重排

上面讨论了两种典型多线程环境下指令重排,分析其带来负面影响,并分别提供了应对方式。

 

  对于关联变量,先封装成一个对象,然后使用原子类来操作对于new对象,使用volatile关键字修饰目标对象即可

 

  

3、synchronized锁与重排序无关

synchronized锁通过互斥锁,有序的保证线程访问特定的代码块。代码块内部的代码正常按照编译器执行的策略重排序。

 

  尽管synchronized锁能够回避多线程环境下重排序带来的不利影响,但是互斥锁带来的线程开销相对较大,不推荐使用。

  synchronized 块里的非原子操作依旧可能发生指令重排

  到此这篇关于Java指令重排序在多线程环境下的应对策略的文章就介绍到这了,更多相关Java指令重排序内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: