volatile型变量语义讲解一 :对所有线程的可见性


一、volatile变量语义一的概念

  当一个变量被定义成volatile之后,具备两个特性:

  特性一:保证此变量对所有线程的可见性。这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量并不能做到这一点,普通变量的值在线程传递时均需要通过主内存来完成。 比如:线程A修改了一个普通变量的值,然后向主内存进行回写,另一条线程B在线程A回写完成了之后再对主内存进行读取操作,新变量值才会对线程B可见。

二、volatile能够保证线程安全吗

  基于volatile变量在各个线程中是不存在一致性问题的,从物理存储的角度看,各个线程的工作内存中volatile变量也可以存在不一致的情况,但是由于每次使用前都要进行刷新,执行引擎看不到不一致的情况,因此也可以人为不存在一致性问题,但是java里面的运算操作符并非是原子操作,这导致了volatile变量的运算在并发下一样是不安全的。

案例代码:

/**
* 测试Volatile的特性
*/
public class VolatileTest {
public static volatile int race = 0; public static void increase(){
race++;
}
//定义线程的数量
private static final int THREADS_COUNT = 20; public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_COUNT];
for(int i = 0;i<THREADS_COUNT;i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0;j<1000;j++){
increase();
}
}
});
threads[i].start();
System.out.println("线程"+i+"开始执行");
} while (Thread.activeCount()>2){
System.out.println("Thread.activeCount() = "+Thread.activeCount());
Thread.yield();//有其他线程等待时,将该线程设置为就绪状态。
}
System.out.println("race:"+race);
}
}

  这段代码发起了20个线程,每个线程都对race变量的做了10000次的自增操作,如果是正常的并发的话,那么race的结果用该是200000,可是执行几次,发现结果并不是200000,而都是一个小于200000的值。这是为什么呢? 因为++操作本身就不是原子的,要经过读取计算和写回,那么,我们通过一张图模仿一下以上代码:

  由于变量被volatile修饰,因此这张图中的3,4操作是连续不间断的,5,6,7的操作也是连续不间断的,但是经过两个线程的读取修改写回操作后,i的值仅仅从1变为了2,并不是我们想象的3,

可能在这里理解上述的图和描述有点抽象,因为有的朋友可能并不能理解数据在主存和缓存中的读取更改的传递规则,在这里,补充一下变量在内存之间的相互操作知识点,大家可以先看以下这块内容,再回过头进行理解上述图中的操作。

三:内存间的相互操作

·lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。

·unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

·read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。

·load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。

·use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。

·assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。

·store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。

·write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

  由volatile修饰的变量的特性保证此线程的可见性可知,当我们使用volatile修饰了一个变量后,一个线程对此变量的修改对于其他线程来讲是立即可知的,也就是说.assign,.store,.write这三个操作是原子的,中间不会间断,会马上的同步主存,就像直接操作主存一样,并通过缓存一致性通知其他的缓存中的副本过期。普通变量可能会在.assign,.store,.write这三个操作中插入其他的操作,导致更改后的数据不能立即同步回主存,这种情况在volatile修饰变量时是不存在的。

四:使用volatile控制并发的场景

由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景中,我们仍然要通过加锁(使用synchronized、java.util.concurrent中的锁或原子类)来保证原子性:

1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。

2、变量不需要与其他的状态变量共同参与不变约束。

举个例子:


class VolatileOne{
volatile boolean isShutDown; public void shutDown(){
isShutDown = true;
} public void dowork(){
while (!isShutDown){
//业务代码
}
}
}
这类场景就比较适合使用volatile控制并发,当 shutDown()方法被调用时,能保证所有线程中执行的dowork()方法都立即停下来。

使用volatile变量的第二个特性是禁止指令重排优化,我们下一篇再来分析。


补充一下,本文参考资料来源于:《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》,其中讲解race++操作时书上用了字节码进行解释的,我这里通过搜索资料,通过一张图来进行的描述,希望可以直观的帮助大家理解。

还有,昨天写的音乐+技术+逗比的结合写法被朋友说写的太乱了,很无厘头,因此这篇中规中矩的写了一篇,整理加自身理解,作者整理不易,觉得能帮助到您的请动动小手点个赞,如有问题请评论区提出。

volatile型变量语义讲解一 :对所有线程的可见性的更多相关文章

  1. Java虚拟机内存模型和volatile型变量

    Java虚拟机内存模型 了解Java虚拟机的内存模型,有助于我们明白为什么会发生线程安全问题. 上面这幅图是<深入理解Java虚拟机-JVM高级特性与最佳实践>的书中截图. 线程共享的变量 ...

  2. volatile型变量自增操作的隐患

      用FindBugs跑自己的项目,报出两处An increment to a volatile field isn't atomic.对应报错的代码例如以下: volatile int num = ...

  3. Java线程角度的内存模型和volatile型变量

    内存模型的目标是定义程序中各个变量的访问 规则,即在虚拟机中将变量(包括实例字段,静态字段和构成数组对象的元素,不包括局部变量与方法参数,因为后者是线程私有的)存储到内存和从内存中取出变量这样的底层细 ...

  4. volatile的内存语义与应用

    volatile的内存语义 volatile的特性 理解volatile特性的一个好方法是把对volatile变量的单个读/写,堪称是使用同一个锁对这些单个读/写操作做了同步. 锁的happens-b ...

  5. synchronized同步块和volatile同步变量

    Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而 ...

  6. volatile的内存语义

    volatile的特性 理解volatile特性的一个好方法是把对volatile变量的单个读/写,堪称是使用同一个锁对这些单个读/写操作做了同步. 锁的happens-before规则保证释放锁和获 ...

  7. JAVA锁和volatile的内存语义&volatile的使用场景

    JAVA锁的内存语义 当线程释放锁时,JMM(Java Memory Model)会把该线程对应的本地内存中的共享变量刷新到主内存中. 当线程获取锁时,JMM会将该线程对应的本地内存置为无效.从而使得 ...

  8. Java内存模型-volatile的内存语义

    一 引言 听说在Java 5之前volatile关键字备受争议,所以本文也不讨论1.5版本之前的volatile.本文主要针对1.5后即JSR-133针对volatile做了强化后的了解. 二 vol ...

  9. Java并发编程原理与实战四十二:锁与volatile的内存语义

    锁与volatile的内存语义 1.锁的内存语义 2.volatile内存语义 3.synchronized内存语义 4.Lock与synchronized的区别 5.ReentrantLock源码实 ...

随机推荐

  1. [V&N公开赛] CheckIn

    V&N战队考核+招新,赵师傅出的Web题,做着感觉有点顶,趁热打铁写write up记录一下考察的知识点 这道题说是CheckIn其实还是有一定难度的(也可能是我太菜了),进入题目直接给出了f ...

  2. Selenium文件上传问题

     

  3. 剑指 Offer 47. 礼物的最大价值

    题目描述 在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0).你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格.直到到达棋盘的右下角.给定一个棋盘及 ...

  4. 跟着兄弟连系统学习Linux-【day09】

    day10-20200609 p29.软件包管理-rpm命令管理-安装升级与卸载 [rpm -ivh 包全名]安装 -i(安装) -v (显示详细信息)-h (显示安装进度) 会一步步依赖,比较麻烦, ...

  5. Oracle12C创建视图权限不足

    授权: GRANT CREATE VIEW TO c##scott; 作者:彼岸舞 时间:2020\06\23 内容关于:Oracle 本文属于作者原创,未经允许,禁止转发

  6. Require.js中的路径在IDEA中的最佳实践

    本文主要讲述require.js在IDEA中路径智能感知的办法和探索中遇到的问题. 测试使用的目录结构:一种典型的thinkphp 6的目录结构,如下图. 现在我通过在 vue-a.js 中运用不同的 ...

  7. 图解选择排序及算法优化(Java实现)

    选择排序 前言 原理:每次循环对比找出最小/大值,将最值的元素交换至左侧 思想:直接选择排序(Straight Select Sort)算法思想:第一趟从n个元素的数据序列中选出关键字最小/大的元素并 ...

  8. App网络测试

    弱网测试作为健壮性测试的重要部分,对于移动端测试必不可少. 什么样的网络属于弱网? 小于或等于2G速率的都属于弱网,且随着5G的到来,3G网络速率的降低,未来3G也会逐渐划入弱网的行列. 对于弱网的数 ...

  9. 【系统之音】Android进程的创建及启动简述

    Android系统中的进程(这里不包括init等底层的进程)都是通过Zygote fork而来的,那这些进程的启动流程都是怎样的呢? 这里将Android进程分为两个部分: (1)系统框架进程Syst ...

  10. 面试官写了个双冒号: : 问我这是什么语法?Java中有这玩意?

    一:简洁 方法引用分为三种,方法引用通过一对双冒号:: 来表示,方法引用是一种函数式接口的另一种书写方式 静态方法引用,通过类名::静态方法名, 如 Integer::parseInt 实例方法引用, ...