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. CentOS yum 安装时错误 Errno 14 Couldn't resolve host 解决办法

    在虚拟机上安装完CentOS6.5之后,首次使用时yum命令安装软件时,出现一堆的” Errno 14 Couldn't resolve host”这个问题. 上网上查了半天,很多都说在/etc/re ...

  2. .Net在Windows上使用Jenkins做CI/CD的那些事

    背景 最近入职了一家新公司,公司各个方面都让我非常的满意,我也怀着紧张与兴奋的心情入职后,在第一天接到了领导给我的第一个任务——把整个项目的依赖引用重新整理并实施项目的CI/CD. 本篇的重点主要分享 ...

  3. ASP.NET Core 进程内与进程外的性能对比

    ASP.NET Core 进程内与进程外的性能对比 本文内容是<深入去浅出ASP.NET Core>提供的扩展内容,毕竟在书里说进程内外的性能说明对比,对于初学者而言,稍微复杂了点. 我在 ...

  4. Spring.Net依赖注入(属性注入)学习笔记

    一.前言: Spring.Net是Java开源框架迁移过来的,主要分为 1)依赖注入 2)面向方面编程 3)数据访问抽象 4)Asp.Net扩展 四个模块功能,这里只是简单介绍依赖注入模块功能. 对于 ...

  5. 2020 最新python入门知识

    1. 基础语法 1.1 注释 在编写代码的时候,有些代码不需要执行或增加代码说明,那么就需要用到注释了. 被注释的文本或代码是不会被执行的. 注释可以使用如下三种方式: # 号 # 第一个注释,本行代 ...

  6. (超详细)动手编写 — 栈、队列 ( Java实现 )

    目录 前言 栈 概念 栈的设计 编码实现 小结 队列 概念 队列的设计 编码实现 双端队列 概念 设计 编码 循环队列 循环队列 循环双端队列 声明 前言 栈 概念 什么是栈? **栈 **:是一种特 ...

  7. JumpServer 架构浅解

    Jumpserver 是一款由python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能.基于ssh协议来管理,客户端无需安装agent.完全开源,GPL授权 设计思路 设计一个跳转网关,所有 ...

  8. virtualbox之紧虚拟主机与本地主机连接

    也就是手工配置IP地址.子网掩码.网关和DNS. 设置方法如下: vi /etc/sysconfig/network-scripts/ifcfg-eth0 编辑本地网卡的配置文件 主要查看下面这几项是 ...

  9. %s 表示格式化一个对象为字符

    比如: name=“good” print('%s'%name)会打印出good print('%3s'%name)  会打印出good,当%和s之间的数字,小于字符串长度时,实际打印出字符串的长度 ...

  10. .net MVC4.0项目发布到阿里云虚拟主机中遇到的问题。

    正所谓学以致用,今天本来想做个bootstrap的demo发到服务器上看一下效果,结果服务器搞了半天,最终太晚了没能学到什么东西. 首先写好页面之后我做了一个MVC4.0的网站项目,然后把Bootst ...