多线程的五个状态(多线程基础volatile关键字详解)

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写操作添加的内存屏障如下

多线程的五个状态(多线程基础volatile关键字详解)(1)

volatile写操作内存屏障插入策略

StoreStore屏障可以保证

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

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

StoreLoad屏障可以保证:

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

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

volatile读操作内存屏障

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

多线程的五个状态(多线程基础volatile关键字详解)(2)

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关键字详解)(3)

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

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页