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. MD5截断比较验证 - 补充

    继上篇MD5截断比较验证文章之后有朋友提示除了数字以外,许多字母数字混合的明文在MD5加密之后也是可以达到同样效果的 同时在De1CTF中Web4题目中遇到了另一种较为特殊的截断比较验证,如图所示: ...

  2. 我竟然才知道slf4j里还有个MDC

    大家好久不见,我是walking.今天给大家带来一个日志方面的知识——MDC,不知道大家认识不,反正我是最近刚知道的 初见MDC 前两天看项目中的代码,无意中看到一个自定义的线程池 MDCThread ...

  3. Centos7.6系统下docker的安装

    一.环境说明 系统:CentOS7.6 软件:Docker19.03 二.Docker的安装 2.1.在线安装 (1) 设置仓库,安装所需的软件包. yum-utils 提供了 yum-config- ...

  4. 最详细不过的CUDA的下载安装使用、环境变量配置,有这一篇就够了

    在上一期中,我们介绍了为什么使用GPU可以加速计算和处理图像,以及查看自己的电脑能否使用GPU加速,不知道的可以去看上一期文章,这期我们正式的来下载与安装GPU加速工具CUDA,并检查是否安装成功. ...

  5. shell小技巧(5)计算当前目录下一级子目录的数量

    方法1: ls -al |grep -E "^d" |grep -v "\.$" |wc -l 详解: -a 列出目录下的所有文件,包含以.开头的隐藏文件: & ...

  6. 剑指offer 07 & LeetCode 105 重建二叉树

    题目 题目链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/ 初步题解 先放代码: /** * Definition for ...

  7. jQuery的那些事儿

    jQuery概述 j-JavaScript+Query就是查询js的库,把js中的DOM操作做了封装,实现快速查询使用其中的功能. 优化了DOM操作.事件处理.动画设计和Ajax交互 学习jQuery ...

  8. git github仓库

    起因 centos 下 git到 github仓 经过 下载git yum install git -y 配置git git config --global user.name "Your ...

  9. linux基础:控制台

    图形界面切换至命令行界面:ctrl+alt+F1~F6 命令行界面切换至图形界面:startx linux一共有7个运行级别,可查看/etc/inittab文件: 0:停机(记住不要把initdefa ...

  10. 备忘录:SQL SERVER2014 出现:“Cannot find one or more components”

    目录 1. 起因 2. 解决方案 3. 备注 4. 参考 2020年9月13日 00:40:09-shanzm 1. 起因 因为卸载vs2015的时候,使用了一个VS2013/2015卸载工具Tota ...