欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

volatile 关键字

程序员文章站 2022-05-04 10:08:59
...
Volatile可以看做一种轻量级的锁,但又和锁有些不同。
这边看个例子,多线程执行generateSerialNumber时候,每次结果都是不同。
private static volatile int serialNum = 0;

public static int generateSerialNumber(){
    return serialNum++;
}
问题出在了 serialNum++ 这条语句上,它不是一个原子化的,实际上是read-modify-write三项操作,这就有可能使得在线程1在write之前,线程2也访问到了nextSerialNum,造成了线程1和线程2得到一样的serialNumber。volatile 关键字

在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,
线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存
变量的具体值load到线程本地内存中(内存中值为写入操作后最新的),建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图


volatile 关键字
这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的


volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。

上面那段话,有两层语义

  1. 保证可见性、不保证原子性
  2. 禁止指令重排序

第一层语义就不做介绍了,下面重点介绍指令重排序。

在执行程序时为了提高性能,编译器和处理器通常会对指令做重排序:

  1. 编译器重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序;
  2. 处理器重排序。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序;

归纳

a) 它对于多线程,不是一种互斥(mutex)关系。
b) 用volatile修饰的变量,
不能保证该变量状态的改变对于其他线程来说是一种“原子化操作”。用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量最新的值。volatile很容易被误用,用来进行原子性操作。
c)指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
d)对volatile变量的写操作 happen-before 后续的读操作。 (详解请跳转:java内存模型之happen-before
f) 有序性即程序执行的顺序按照代码的先后顺序执行.

所以,在使用Volatile时,需要注意
1.对变量的写操作不依赖当前值;
2.该变量没有包含在具有其他变量的不变式中。