百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

「多线程基础」volatile关键字详解

toyiye 2024-07-03 02:07 15 浏览 0 评论

volatile的原理

正常情况下,操作系统不会进行共享变量的缓存一致性校验,只有当共享变量被volatile关键字修饰了,该变量所在的缓存行才被要求进行缓存一致性的校验。

下面通过volatile关键字的汇编代码,分析一下volatile关键字的底层原理。Java代码如下:

package org.cloudxue.visiable;

public class VolatileVar {
    volatile int var = 0;

    public void setVar (int var){
        System.out.println("setVar = " + var);
        this.var = var;
    }

    public static void main(String[] args) {
        VolatileVar var = new VolatileVar();
        var.setVar(20);
    }
}

以上代码通过IDEA编辑,进入到class目录,执行以下命令:

?  classes git:(main) pwd
/Users/xuexiao/Work/QDBank/Idea-WorkSpace/SpringBootMuiltModule/multi_thread_lecture/target/classes
?  classes git:(main) java -server -Xcomp -XX:-Inline -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly org/cloudxue/visiable/VolatileVar > ~/Downloads/volatile.log
Java HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output
?  classes git:(main) 


命令选项说明:
-Xcomp:表示永远以编译模式运行,禁止解释器模式
-XX:Inline:禁止内联优化
-server:设置虚拟机使用“server”模式。该模式启动比“client”模式慢,但是运行性能高,Linux缺省使用server模式

输出的volatile.log日志文件较大,选取其中的关键部分

0x000000010c7c3c54: mov    %r12d,0xc(%rbp)
0x000000010c7c3c58: lock addl $0x0,(%rsp)     ;*putfield var
                                              ; - org.cloudxue.visiable.VolatileVar::<init>@6 (line 11)

0x000000010c7c3c5d: add    $0x10,%rsp
0x000000010c7c3c61: pop    %rbp

通过以上汇编指令可以看到,操作var共享变量之前,多出一个lock前缀指令:local addl,该lock前缀指令有三个功能:

  1. 将当前处理器缓存行的数据立即写回系统内存。Lock前缀指令导致在执行期间,CPU可以独占共享内存;
  2. lock前缀指令会引起在其他CPU里缓存了该内存地址的数据无效写回操作要经过总线传播数据,而每个处理器通过嗅探在总线是哪个传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态,当处理器要对这个值进行修改时,会强制重新从系统内存里把数据读到处理器缓存;
  3. lock前缀指令禁止指令重排序

总之,volatile关键词保障内存可见性的原理,涉及到MESI协议、内存屏障等硬件层面的技术。

volatile语义中的内存屏障

在Java代码中,volatile关键字主要有两层语义:

  • 不同线程对volatile变量的值具有内存可见性。即一个线程修改了某个volatile变量的值,该值对其他线程立即可见;
  • 禁止指令重排序

volatile关键字除了保障内存可见性,还能确保执行的有序性。volatile语义中的有序性,是通过内存屏障指令来确保的。为了实现volatile关键字的有序性,JVM编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

JMM建议JVM采取保守策略对重排序进行严格禁止,下面是基于保守策略的volatile操作的内存屏障插入策略:

  • 在每个volatile写操作的前面,插入一个StoreStore屏障;
  • 在每个volatile写操作的后面,插入一个StoreLoad屏障;
  • 在每个volatile读操作的后面,插入一个LoadLoad屏障;
  • 在每个volatile读操作的后面,插入一个LoadStore屏障;

volatile写操作内存屏障

JVM采用保守策略,为volatile写操作添加的内存屏障如下


StoreStore屏障可以保证

  • 前面的写操作不会重排到后面;
  • 前面的写完成后,高速缓存里的结果数据刷入主存;
  • 后面的写操作不会重排到前面

所以,在volatile写入之前,其前面的所有普通写操作已经对任意处理器可见了,并且volatile写操作不会被重排到屏障前面去。

StoreLoad屏障可以保证:

  • 前面的写操作不会重排的后面;
  • 前面的写完成后,高速缓存里的结果数据刷入主存;
  • 让高速缓存中的数据失效,重新从主存加载数据;
  • 后面的读操作不会重排到前面去

所以,在volatile写完成后,高速缓存已经刷新成最新数据,对后面所有处理器的读操作都可见了。

volatile读操作内存屏障

JVM采用保守策略,为volatile读操作添加的内存屏障如下:


LoadLoad屏障可以保证:

  • 前面的volatile读操作不会被排到后面去;
  • 让所有高速缓存中的数据失效,重新从主存中拉取数据;
  • 后面的读操作不会被排到前面去;

所以,在volatile读之后,所有高速缓存中的数据都是从主存加载且是最新的;

LoadStore屏障可以保证:

  • 前面的volatile读操作不会被排到后面去;
  • 让所有高速缓存中的数据失效,重新从主内存中拉取数据;
  • 后面的写操作不会被排到前面去;

所以,volatile读之后,所有高速缓存中的数据都是从主内存加载且是最新的。

实际上,以上JMM建议的对volatile写和volatile读的内存屏障插入策略是针对任意处理器平台的,是非常保守的,不同处理器有不同“松紧度”的处理器内存模型,只要不改变volatile读写操作的内存语义,不同JVM编译器可以根据实际情况省略不必要的JMM屏障。

volatile变量写入的操作性能提升

对每一个volatile变量的每一次读写,JVM都会插入一系列的内存屏障,这就意味着对一个volatile变量每一次写入操作,性能是较低的。尤其是StoreLoad屏障,为了维护内核缓存的强一致性,此屏障可谓不惜血本。

那么在不是每一次对volatile变量的读写都需要保证其内存可见性,或者说,仅仅是某次特定的volatile读写需要保障可见性,可以使用如下两个策略对volatile进行性能优化:

  • 对于普通变量,使用Unsafe.putXXXVolatile()或者Unsafe.getXXXVolatile(),保障单次操作的内存可见性,或有序性。XXX代表基本数据类型如Object、int、long等。其中Unsafe.putXXXVolatile()等效于在写入前后,插入StoreStore屏障、StoreLoad屏障。Unsafe.getXXXVolatile()等效于在在读取后面插入LoadLoad屏障、LoadStore屏障。这种通过API的方式,手动让编译后的代码插入内存屏障,实现volatile语义以保证内存可见性和防止指令重排。这种方式的优势在于,粒度更小,只对需要的地方进行内存可见性保证和指令重排序的禁止,而不是像volatile关键字一样,所有的读写操作都自动插入内存屏障。
  • 对于volatile修饰的变量如果写入的时候,不需要保障立即可见,或者说不需要保障不同CPU缓存之间的强一致性,可以使用Unsafe.putOrderXXX()方法写入,保障写入操作的有序性即可。此种做法,被Netty、JCTools采用来提升性能。这种方式等效于在写入前面插入StoreStore屏障,由于不保障可见,所以去掉了插入在写入操作后面的StoreLoad屏障。正是由于去掉了这个屏障,此方法是对volatile变量写入操作的一次显著的性能提升。StoreStore屏障只保证禁止重排,不保证内存可见性,从而实现了一次轻量级的写入,在特定场景能优化性能,保障最终一致性。当然,性能提升是有代价的,写入的结果不会立马被其他线程见到。

volatile不具备原子性

使用volatile可以保证数据的可见性,但不能保证数据的原子性,对于volatile修饰的变量进行注入自增的复合操作,仍会存在线程不安全问题。如:

package org.cloudxue.visiable;

import org.cloudxue.common.util.Print;
import org.cloudxue.common.util.ThreadUtil;
import org.junit.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;

public class VolatileDemo {
    private volatile long value;

    @Test
    public void testVolatileAtomic() throws InterruptedException{
        final int TASK_AMOUNT = 10;

        ExecutorService pool = ThreadUtil.getCpuIntenseTargetThreadPool();

        CountDownLatch latch = new CountDownLatch(TASK_AMOUNT);

        final int TURN = 10000;
        long start = System.currentTimeMillis();
        for (int i = 0; i < TASK_AMOUNT; i++) {
            pool.submit(() -> {
                for(int j = 0; j < TURN; j++) {
                    value++;
                }
                latch.countDown();
            });

        }

        latch.await();

        float time = (System.currentTimeMillis() - start) / 1000F;

        Print.tcfo("运行时长" + time);
        Print.tcfo("累加结果为: " + value);
        Print.tcfo("与预期差值: " + (TASK_AMOUNT* TURN - value));
    }
}

=============
/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/bin/java -ea -Didea.test.cyclic.buffer.size=1048576 -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=65474:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit5-rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit-rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/lib/tools.jar:/Users/xuexiao/Work/QDBank/Idea-WorkSpace/SpringBootMuiltModule/multi_thread_lecture/target/classes:/Users/xuexiao/Work/QDBank/Idea-WorkSpace/SpringBootMuiltModule/democommon/target/classes:/Users/xuexiao/.m2/repository/org/springframework/boot/spring-boot-starter/2.3.4.RELEASE/spring-boot-starter-2.3.4.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/boot/spring-boot/2.3.4.RELEASE/spring-boot-2.3.4.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.3.4.RELEASE/spring-boot-autoconfigure-2.3.4.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.3.4.RELEASE/spring-boot-starter-logging-2.3.4.RELEASE.jar:/Users/xuexiao/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/xuexiao/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/xuexiao/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.13.3/log4j-to-slf4j-2.13.3.jar:/Users/xuexiao/.m2/repository/org/apache/logging/log4j/log4j-api/2.13.3/log4j-api-2.13.3.jar:/Users/xuexiao/.m2/repository/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar:/Users/xuexiao/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/xuexiao/.m2/repository/org/yaml/snakeyaml/1.26/snakeyaml-1.26.jar:/Users/xuexiao/.m2/repository/org/projectlombok/lombok/1.16.10/lombok-1.16.10.jar:/Users/xuexiao/.m2/repository/com/alibaba/easyexcel/3.0.5/easyexcel-3.0.5.jar:/Users/xuexiao/.m2/repository/org/apache/poi/poi/4.1.2/poi-4.1.2.jar:/Users/xuexiao/.m2/repository/commons-codec/commons-codec/1.13/commons-codec-1.13.jar:/Users/xuexiao/.m2/repository/org/apache/commons/commons-collections4/4.4/commons-collections4-4.4.jar:/Users/xuexiao/.m2/repository/org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar:/Users/xuexiao/.m2/repository/com/zaxxer/SparseBitSet/1.2/SparseBitSet-1.2.jar:/Users/xuexiao/.m2/repository/org/apache/poi/poi-ooxml/4.1.2/poi-ooxml-4.1.2.jar:/Users/xuexiao/.m2/repository/org/apache/commons/commons-compress/1.19/commons-compress-1.19.jar:/Users/xuexiao/.m2/repository/com/github/virtuald/curvesapi/1.06/curvesapi-1.06.jar:/Users/xuexiao/.m2/repository/org/apache/poi/poi-ooxml-schemas/4.1.2/poi-ooxml-schemas-4.1.2.jar:/Users/xuexiao/.m2/repository/org/apache/xmlbeans/xmlbeans/3.1.0/xmlbeans-3.1.0.jar:/Users/xuexiao/.m2/repository/org/apache/commons/commons-csv/1.8/commons-csv-1.8.jar:/Users/xuexiao/.m2/repository/cglib/cglib/3.3.0/cglib-3.3.0.jar:/Users/xuexiao/.m2/repository/org/ow2/asm/asm/7.1/asm-7.1.jar:/Users/xuexiao/.m2/repository/org/slf4j/slf4j-api/1.7.32/slf4j-api-1.7.32.jar:/Users/xuexiao/.m2/repository/org/ehcache/ehcache/3.8.1/ehcache-3.8.1.jar:/Users/xuexiao/.m2/repository/org/glassfish/jaxb/jaxb-runtime/2.3.1/jaxb-runtime-2.3.1.jar:/Users/xuexiao/.m2/repository/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar:/Users/xuexiao/.m2/repository/org/glassfish/jaxb/txw2/2.3.1/txw2-2.3.1.jar:/Users/xuexiao/.m2/repository/com/sun/istack/istack-commons-runtime/3.0.7/istack-commons-runtime-3.0.7.jar:/Users/xuexiao/.m2/repository/org/jvnet/staxex/stax-ex/1.8/stax-ex-1.8.jar:/Users/xuexiao/.m2/repository/com/sun/xml/fastinfoset/FastInfoset/1.2.15/FastInfoset-1.2.15.jar:/Users/xuexiao/.m2/repository/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar:/Users/xuexiao/.m2/repository/com/alibaba/fastjson/1.2.76/fastjson-1.2.76.jar:/Users/xuexiao/.m2/repository/org/openjdk/jol/jol-core/0.11/jol-core-0.11.jar:/Users/xuexiao/.m2/repository/junit/junit/4.11/junit-4.11.jar:/Users/xuexiao/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/xuexiao/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.3.4.RELEASE/spring-boot-starter-web-2.3.4.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.3.4.RELEASE/spring-boot-starter-json-2.3.4.RELEASE.jar:/Users/xuexiao/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2.jar:/Users/xuexiao/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2.jar:/Users/xuexiao/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.11.2/jackson-core-2.11.2.jar:/Users/xuexiao/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2.jar:/Users/xuexiao/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2.jar:/Users/xuexiao/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.11.2/jackson-module-parameter-names-2.11.2.jar:/Users/xuexiao/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.3.4.RELEASE/spring-boot-starter-tomcat-2.3.4.RELEASE.jar:/Users/xuexiao/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.38/tomcat-embed-core-9.0.38.jar:/Users/xuexiao/.m2/repository/org/glassfish/jakarta.el/3.0.3/jakarta.el-3.0.3.jar:/Users/xuexiao/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.38/tomcat-embed-websocket-9.0.38.jar:/Users/xuexiao/.m2/repository/org/springframework/spring-web/5.2.9.RELEASE/spring-web-5.2.9.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/spring-beans/5.2.9.RELEASE/spring-beans-5.2.9.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/spring-webmvc/5.2.9.RELEASE/spring-webmvc-5.2.9.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/spring-aop/5.2.9.RELEASE/spring-aop-5.2.9.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/spring-context/5.2.9.RELEASE/spring-context-5.2.9.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/spring-expression/5.2.9.RELEASE/spring-expression-5.2.9.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/boot/spring-boot-starter-test/2.3.4.RELEASE/spring-boot-starter-test-2.3.4.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/boot/spring-boot-test/2.3.4.RELEASE/spring-boot-test-2.3.4.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/boot/spring-boot-test-autoconfigure/2.3.4.RELEASE/spring-boot-test-autoconfigure-2.3.4.RELEASE.jar:/Users/xuexiao/.m2/repository/com/jayway/jsonpath/json-path/2.4.0/json-path-2.4.0.jar:/Users/xuexiao/.m2/repository/net/minidev/json-smart/2.3/json-smart-2.3.jar:/Users/xuexiao/.m2/repository/net/minidev/accessors-smart/1.2/accessors-smart-1.2.jar:/Users/xuexiao/.m2/repository/jakarta/xml/bind/jakarta.xml.bind-api/2.3.3/jakarta.xml.bind-api-2.3.3.jar:/Users/xuexiao/.m2/repository/jakarta/activation/jakarta.activation-api/1.2.2/jakarta.activation-api-1.2.2.jar:/Users/xuexiao/.m2/repository/org/assertj/assertj-core/3.16.1/assertj-core-3.16.1.jar:/Users/xuexiao/.m2/repository/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar:/Users/xuexiao/.m2/repository/org/junit/jupiter/junit-jupiter/5.6.2/junit-jupiter-5.6.2.jar:/Users/xuexiao/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.6.2/junit-jupiter-api-5.6.2.jar:/Users/xuexiao/.m2/repository/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar:/Users/xuexiao/.m2/repository/org/junit/platform/junit-platform-commons/1.6.2/junit-platform-commons-1.6.2.jar:/Users/xuexiao/.m2/repository/org/junit/jupiter/junit-jupiter-params/5.6.2/junit-jupiter-params-5.6.2.jar:/Users/xuexiao/.m2/repository/org/junit/jupiter/junit-jupiter-engine/5.6.2/junit-jupiter-engine-5.6.2.jar:/Users/xuexiao/.m2/repository/org/junit/vintage/junit-vintage-engine/5.6.2/junit-vintage-engine-5.6.2.jar:/Users/xuexiao/.m2/repository/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar:/Users/xuexiao/.m2/repository/org/junit/platform/junit-platform-engine/1.6.2/junit-platform-engine-1.6.2.jar:/Users/xuexiao/.m2/repository/org/mockito/mockito-core/3.3.3/mockito-core-3.3.3.jar:/Users/xuexiao/.m2/repository/net/bytebuddy/byte-buddy/1.10.5/byte-buddy-1.10.5.jar:/Users/xuexiao/.m2/repository/net/bytebuddy/byte-buddy-agent/1.10.5/byte-buddy-agent-1.10.5.jar:/Users/xuexiao/.m2/repository/org/objenesis/objenesis/2.6/objenesis-2.6.jar:/Users/xuexiao/.m2/repository/org/mockito/mockito-junit-jupiter/3.3.3/mockito-junit-jupiter-3.3.3.jar:/Users/xuexiao/.m2/repository/org/skyscreamer/jsonassert/1.5.0/jsonassert-1.5.0.jar:/Users/xuexiao/.m2/repository/com/vaadin/external/google/android-json/0.0.20131108.vaadin1/android-json-0.0.20131108.vaadin1.jar:/Users/xuexiao/.m2/repository/org/springframework/spring-core/5.2.9.RELEASE/spring-core-5.2.9.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/spring-jcl/5.2.9.RELEASE/spring-jcl-5.2.9.RELEASE.jar:/Users/xuexiao/.m2/repository/org/springframework/spring-test/5.2.9.RELEASE/spring-test-5.2.9.RELEASE.jar:/Users/xuexiao/.m2/repository/org/xmlunit/xmlunit-core/2.7.0/xmlunit-core-2.7.0.jar com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 org.cloudxue.visiable.VolatileDemo,testVolatileAtomic
[main|VolatileDemo.testVolatileAtomic]:运行时长0.064
[main|VolatileDemo.testVolatileAtomic]:累加结果为: 48699
[main|VolatileDemo.testVolatileAtomic]:与预期差值: 51301

为什么volatile变量的复合操作不具备原子性呢。首先回顾一下JMM对变量的读取和写入的流程:

对于普通的非volatile变量而言,JMM要求保持read、load有相对顺序即可。

对于关键字volatile修饰的变量而言,具有两个重要的语义:

  1. volatile修饰的变量在变量值发生改变的时候,会立刻同步到主内存,并使其他线程的变量副本失效;
  2. 禁止指令重排序。硬件层面通过在指令前后加入内存屏障来实现,编译器级别则通过下面的规则来实现:

a、使用volatile修饰的变量,其read、load、use操作都是连续出现的,所以每次使用变量的时候,都从主内存读取最新的变量值,替换私有内存中的变量副本值;

b、对同一变量的assign、store、write操作都是连续出现的,所以每次对变量的改变都会立马同步到主内存中。

虽然volatile修饰的变量可以强制刷新内存,其要求对变量读相关(read、load、use)、写相关(assign、store、write)操作连续出现,但是这些操作可能在不同的CPU内核上执行,还是可能出现脏数据的情况。

例如,上述VolatileDemo的代码,线程A、线程B分别运行在Core1、Core2上,value值为0。线程A、B都将value值读取到自己的工作内存中。现在线程A将value变成1之后,完成了assign、store操作,但是在执行write操作时,CPU的时间片用完了,线程A被闲置,write操作没有达到主存。由于线程A的store指令触发了写的信号,线程B缓存过期,重新从主存中读取value,此时value还是0。当线程B执行完所有的操作后,将value值变为1写入主存。此时线程A又拿到了时间片,重新执行store操作,将过期的1,写入主存。


所以,对于复合操作,volatile变量无法保证原子性。若需保证复合操作的原子性,通常需要使用锁。在高并发场景下volatile变量一定要与Java的显示锁结合使用

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码