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. 浅析LR.Net工作流引擎

    在当代信息化软件系统开发中,工作流引擎是其中非常重要的一环.所谓工作流引擎,是指工作流作为软件系统的一部分, 其中包括了流程的节点管理.流向管理.流程样例管理.审核管理等重要功能. 工作流引擎可根据角 ...

  2. Docker 学习笔记一

    Docker 学习笔记一 1.Docker是什么?         Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源.让开发者打包他们的应用以及依赖包到一 ...

  3. oracle之复杂查询(下):子查询

    复杂查询(下):子查询 8. 1 非关联子查询:返回的值可以被外部查询使用.子查询可以独立执行的(且仅执行一次). 8.1.1 单行单列子查询,子查询仅返回一个值,也称为标量子查询,采用单行比较运算符 ...

  4. Tomcat vs Jetty vs Undertow性能对比

    Tomcat,Jetty和Undertow是目前比较主流的3款Servlet容器,而且Spring Boot框架还提供了对它们的集成支持(默认使用的是Tomcat),网络上有许多文章都在介绍Under ...

  5. CTF-WeChall-第二天

    2020.09.10 奥力给,举步维艰的时候就是要一边做一遍记,虽然慢但是不要嫌弃,要不然就是举步不前

  6. 小白的springboot之路(十七)、阿里云OSS 的使用

    0-前言 项目中,文件服务必不可少,常用的有各云服务商的OSS服务(如阿里云OSS,腾讯云OSS,七牛云).自建(fastDFS.minio): 推荐:如果用云服务的话,阿里云OSS很方便,如果自建的 ...

  7. rabbitmq的安装&学习

    主要按照 https://www.cnblogs.com/web424/p/6761153.html https://www.cnblogs.com/qiyebao/p/4822583.html 学习 ...

  8. ribbon源码(3) 配置模块

    ribbon的很多功能可以通过配置进行调整,ribbon通过IClientConfig来获取配置信息,用户可以通过实现IClientConfig来管理配置. ribbon也提供了默认的实现(Defau ...

  9. kubernetes部署Percona XtraDB Cluster集群

    PXC介绍 全称percona-xtradb-cluster,提供了MySQL高可用的一种实现方法.PXC集群以节点组成(推荐至少3节点,便于故障恢复),每个节点都是基于常规的 MySQL Serve ...

  10. Ubuntu18.04安装常用软件指南

    安装中文版火狐浏览器 第一步:先卸载:sudo apt-get remove firefox第二步:安装:sudo apt-get install firefox第三步:设置成中文:sudo apt- ...