这个volatile用对了吗?

一、背景

在项目中有这么一段代码,其中configs 这个变量使用了static volatile 两个关键字;对于这个变量是否需要加 volatile 关键字,有点疑惑。其中 load 方法是在项目启动和new 线程中读取到新的配置会调用;而isHitGray 方法在大量的业务代码中被调用。

java 复制代码
public static volatile String con'fi'g's;
public static synchronized void load(Map<String, String> merchantConfigContent ) {
    configs=merchantConfigContent.get(KEY);
}
public static boolean isHitGray(String planId){
    if (StringUtils.isBlank(planId)) {
        return false;
    }
    return configs.contains(planId);
}

二、volatile关键字

当一个变量被定义为volatile 之后会获得两项特性:可见性和禁止指令重排。

1. 可见性

在java中,每一个线程都有一个自己的工作内存,在默认情况下,为了提高效率,会将共享变量拷贝一份到自己的工作线程当中,例如上面的configs变量,如下图。但是如果其中一个线程对共享变量进行了更新之后,会导致其他线程中的共享变量并不是最新的的,就会导致数据不一致的问题。而加了volatile之后,java工作线程更新之后,会同步其他工作线程,其他工作线程读取共享变量的时候,发现已过期会重新从主内存中读取,由此保证可见性。

下面的代码中展示了,如果不使用 volatile,会由于可见性导致数据异常。

java 复制代码
public class VolatileTest {

    private static int value = 0;

    @Test
    public void test() throws InterruptedException {

        Thread[] threads = new Thread[10];

        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    value+=1;
                }
            });
        }
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        System.out.println(value);
    }
}
输出结果:
Picked up _JAVA_OPTIONS: -DTEST_HOME_DIR=/Users/didi
9064

2. 禁止指令重排

使用volatile 变量的第二个语义是禁止指令重排序优化,普通的变量仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证 "变量赋值操作"的顺序与程序代码中的执行顺序一致。因为在同一个线程的方法执行过程中无法感知到这点,这就是Java内存模型中描述的所谓"线程内表现为串行的语义"(As-If-Serial)。

例如下面这段伪代码,如果flag = true 的操作提前,会导致 buy 中的操作异常。

java 复制代码
private static boolean flag = false;
public void update() {
    //这里做商品上架,增加库存的操作
    add();
    flag = true;
}
public void buy() {
    while (!flag) {
        //等待
        sleep();
    }
    //这里做库存扣减的操作
    del();
}

三、synchronized关键字

synchronized是jvm提供的一个同步关键字,通过synchronized关键字,我们可以实现"一个变量在同一个时刻只允许一条线程对其进行lock操作"。synchronized关键字可以使用在代码块和方法上。当synchronized关键字加在方法上的时候,此时synchronized方法会获取当前类的类锁。但是需要注意,synchronized方法并不会阻塞其他非synchronized方法。

四、结论

那么回归最开始的问题, configs 在load方法中进行写操作,在isHitGray 方法进行读操作;load方法是synchronized 方法,但是 isHitGray 方法却不是,因此当load方法在更新 configs 变量的时候,isHitGray方法并不会阻塞;因此可能会出现更新后,其他线程在调用 isHitGray 方法的时候,获取到的不是最新的变量,因此我们需要通过 volatile 关键字保证可见性。

最终我们可以得到一个结论: configs 变量上的 volatile 关键字是必要的,为了保证并发情况下的可见性。

相关推荐
Linux520小飞鱼1 小时前
F#语言的网络编程
开发语言·后端·golang
小小小妮子~1 小时前
设计模式七大设计原则Java 实践
java·设计模式
BinaryBardC5 小时前
Bash语言的数据类型
开发语言·后端·golang
Pandaconda5 小时前
【Golang 面试题】每日 3 题(二十一)
开发语言·笔记·后端·面试·职场和发展·golang·go
_院长大人_6 小时前
使用 Spring Boot 实现钉钉消息发送消息
spring boot·后端·钉钉
土豆凌凌七6 小时前
GO随想:GO的并发等待
开发语言·后端·golang
AI向前看6 小时前
C语言的数据结构
开发语言·后端·golang
快乐非自愿7 小时前
一文解秘Rust如何与Java互操作
java·开发语言·rust
SomeB1oody7 小时前
【Rust自学】10.8. 生命周期 Pt.4:方法定义中的生命周期标注与静态生命周期
开发语言·后端·rust
小万编程7 小时前
基于SpringBoot+Vue毕业设计选题管理系统(高质量源码,提供文档,免费部署到本地)
java·vue.js·spring boot·计算机毕业设计·java毕业设计·web毕业设计