Java指令重排在多线程环境下的解决方式

 更新时间:2022年04月23日 17:31:43   作者:Java知识图谱  
这篇文章介绍了Java指令重排在多线程环境下的解决方式,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一、序言

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

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

二、问题复原

(一)关联变量

下面给出一个能够百分之百复原指令重排的例子。

public class D {
    static Integer a;
    static Boolean flag;
    
    public static void writer() {
        a = 1;
        flag = true;
    }
    
    public static void reader() {
        if (flag != null && flag) {
            System.out.println(a);
            a = 0;
            flag = false;
        }
    }
}

1、结果预测

reader方法仅在flag变量为true时向控制台打印变量a的值。

writer方法先执行变量a的赋值操作,后执行变量flag的赋值操作。

如果按照上述分析逻辑,那么控制台打印的结果一定全为1。

2、指令重排

假如代码未发生指令重排,那么当flag变量为true时,变量a一定为1。

上述代码中关于变量a和变量flag在两个方法类均存在指令重排的情况。

public static void writer() {
    a = 1;
    flag = true;
}

通过观察日志输出,发现有大量的0输出。

writer方法内部发生指令重排时,flag变量先完成赋值,此时假如当前线程发生中断,其它线程在调用reader方法,检测到flag变量为true,那么便打印变量a的值。此时控制台存在超出期望值的结果。

(二)new创建对象

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

public class Singleton {
    private static UserModel instance;
    
    public static UserModel getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new UserModel(2, "B");
                }
            }
        }
        return instance;
    }
}

@Data
@AllArgsConstructor
class UserModel {
    private Integer userId;
    private String userName;
}

1、解析创建过程

  • 使用关键字new创建一个对象,大致分为一下过程:
  • 在栈空间创建引用地址
  • 以类文件为模版在堆空间对象分配内存
  • 成员变量初始化
  • 使用构造函数初始化
  • 将引用值赋值给左侧存储变量

2、重排序过程分析

针对上述示例,假设第一个线程进入synchronized代码块,并开始创建对象,由于重排序存在,正常的创建对象过程被打乱,可能会出现在栈空间创建引用地址后,将引用值赋值给左侧存储变量,随后因CPU调度时间片耗尽而产生中断的情况。

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

三、应对指令重排

(一)AtomicReference原子类

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public 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
@AllArgsConstructor
class UserModel {
    private Integer userId;
    private String userName;
}

四、指令重排的理解

1、指令重排广泛存在

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

2、多线程环境指令重排

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

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

3、synchronized锁与重排序无关

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

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

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

到此这篇关于Java多线程环境指令重排的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • java实现双人五子棋游戏

    java实现双人五子棋游戏

    这篇文章主要为大家详细介绍了java实现双人五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • logback自定义json日志输出示例详解

    logback自定义json日志输出示例详解

    这篇文章主要为大家介绍了logback自定义json日志输出,就是通过logback日志体系以及logstash提供的json log依赖将数据以json格式记录到日志文件的例子
    2022-03-03
  • SpringBoot Controller Post接口单元测试示例

    SpringBoot Controller Post接口单元测试示例

    今天小编就为大家分享一篇关于SpringBoot Controller Post接口单元测试示例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • spring boot(四)之thymeleaf使用详解

    spring boot(四)之thymeleaf使用详解

    Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP 。接下来通过本文给大家介绍spring boot(四)之thymeleaf使用详解,需要的朋友可以参考下
    2017-05-05
  • Java Swing JPanel面板的使用方法

    Java Swing JPanel面板的使用方法

    这篇文章主要介绍了Java Swing JPanel面板的使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • SpringMVC整合kinfe4j及问题解决分析

    SpringMVC整合kinfe4j及问题解决分析

    这篇文章主要为大家介绍了SpringMVC整合kinfe4j及问题解决分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • SpringCloud Feign 服务调用的实现

    SpringCloud Feign 服务调用的实现

    Feign是一个声明性web服务客户端。本文记录多个服务之间使用Feign调用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-01-01
  • java 实现反射 json动态转实体类--fastjson

    java 实现反射 json动态转实体类--fastjson

    这篇文章主要介绍了java 实现反射 json动态转实体类--fastjson,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Mybatis使用@Select注解sql中使用in问题

    Mybatis使用@Select注解sql中使用in问题

    这篇文章主要介绍了Mybatis使用@Select注解sql中使用in问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • Java并发工具之Exchanger线程间交换数据详解

    Java并发工具之Exchanger线程间交换数据详解

    这篇文章主要介绍了Java并发工具之Exchanger线程间交换数据详解,Exchanger是一个用于线程间协作的工具类,Exchanger用于进行线程间的数据交 换,它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据,需要的朋友可以参考下
    2023-12-12

最新评论