volatile关键字的详解-并发编程的体现
xl_echo编辑整理,欢迎转载,转载请声明文章来源。欢迎添加echo微信(微信号:t2421499075)交流学习。 百战不败,依不自称常胜,百败不颓,依能奋力前行。——这才是真正的堪称强大!!
参考书籍:《Java高并发编程详解》。尊重原创,支持知识付费,以下内容标记有摘抄的为该书内容,如需查看该书的对应知识点,请购买原版书籍。
参考文章列表:
volatile
什么是volatile
volatile和synchronized相比,volatile被称为轻量级锁,并且能实现部分synchronized的语义。它在处理多线程并发的时候主要保证了共享资源的可见性,该功能可以理解为一个线程修改某一个共享变量的时候,另外一个变量可以读到该共享变量的值。
资源无可见性在代码中产生的问题
基于了解程序原理就先上代码观察结果的思想,我们可以通过观察下面这段代码,先对volatile的基本体现有一个了解。
以下代码来自摘抄
public class VolatileFoo {
final static int MAX = 5;
static int init_value = 0;
//static volatile int init_value = 0;
public static void main(String[] args) {
new Thread(() -> {
int localValue = init_value;
while (localValue < MAX) {
if (init_value != localValue) {
System.out.printf("The init_value is update to [%d]\n", init_value);
localValue = init_value;
}
}
}, "Reader").start();
new Thread(() -> {
int localValue = init_value;
while (localValue < MAX) {
System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
init_value = localValue;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Updater").start();
}
}
如果我们对volatile没有了解的情况下,我们看到以上代码会觉得这就是实现创建了两个线程不断的去交替输出一个变量的功能。观看代码确实就是localValue不断的变更,直到localValue等于5的时候,while循环才结束。但是我们可以看到以下结果,与这里的结论不符,证明我们的结论有误
实际的输出结果是我们的Updater线程一直在输出,当localValue=5的时候,我们的Updater线程就会结束循环。而我们的Reader线程却一直在执行,并是处于死循环的状态。这里我们可以看到Reader线程读到的localValue的值应该是一直没有改变,也能够明显的看到一个问题,那就是两个线程访问一个变量,Updater修改变量的值后Reader线程并没有获取到这个值的变化。
volatile的初体验
出现以上问题之后,我们可以看到共享变量的可见性它的重要性,解决上面程序的问题其实也比较简单,只需要在上面做一个小修改即可。将init_value使用volatile进行修饰,其他的不变,我们再一次观察输出结果。
通过输出结果我们可以看到,当Updater线程对init_value进行了改变之后,我们的Reader线程有效的观察到了这个变量的变化,并且跟着输出了Reader线程观察到init_value的结果。和上面不一样的在于,这段代码都能有效的结束程序
深入了解volatile,并有效掌握实现的方式还需要去了解->CPU硬件的运行和JMM内存模型。CPU与我们程序运行之间的关系是怎么样的,底层是怎么出现这些错误的使我们了解volatile必不可少需要掌握的要点。
我们编写的程序与CPU执行的关系
在我们的程序运行中,我们一个程序被CPU有效执行的一个过程要经过写入硬盘,内存加载,CPU访问执行。,硬盘、内存和CPU之间又有很大的区别。
- 硬盘:存储资料和软件等数据的设备,有容量大,断电数据不丢失的特点。也被人们称之为“数据仓库”。我们编写的程序就是存储在硬盘里面
- 内存:1. 负责硬盘等硬件上的数据与CPU之间数据交换处理;2. 缓存系统中的临时数据。3. 断电后数据丢失。可以称为他就是硬盘和CPU之间的桥梁,并且我们的程序编写完成存储到硬盘中之后,开始执行就会被加载进入内存,并等待CPU对内存进行寻址操作。
- CPU:中央处理单元(Cntral Pocessing Uit)的缩写,也叫处理器,是计算机的运算核心和控制核心。执行我们编写的代码CPU只是接收到执行指令,然后对内存进行寻址操作。
硬盘、内存和CPU的存取速度是递增的,内存比硬盘要快很多,但是CPU又比内存块很多倍,CPU的存取速度快到内存都跟不上,所以在CPU和内存之间出现了一个新的东西,那就是CPU Cache。cache的容量远远小于主存,因此出现cache miss在所难免,这也是我们为什么会出现数据问题的关键所在。
CPU cache结构及cache操作数据导致数据不一致的问题
CPU中cache的结构我们可以打开任务管理器,点击性能即可以看到。多核CPU的结构与单核相似,但是多了所有CPU共享的L3三级缓存。在多核CPU的结构中,L1和L2是CPU私有的,L3则是所有CPU核心共享的。
在我们的系统中,由于短板效应,导致即时我们CPU速度再快也没有办法发挥它的能力。由于内存的读取速度远低于CPU,所以导致我们的程序执行速度,被内存限制。但是当cache出现之后完全改变了这个情况,它极大的增大了CPU的吞吐量。CPU只需要到cache中进行读取和写入操作即可,cache会在之后将结果同步到内存。但是当多线程情况下就会出现问题,每个线程都有自己的工作内存,本地内存,对应CPU中Cache。当多个线程同时操作一个变量的时候,都会进行读取到CPU Cache当中,然后在同步会主内存,这样就会导致数据结果不一致。也就是我们平时看到的,多线程结果与预期不一致的问题。
Java内存模型
Java的内存模型(Java Memory Mode)制定了Java虚拟机如何与计算机的主存进行工作,理解Java内存模型对于编写并发程序非常重要。在CPU cache当中我们使用文字描述了多线程情况下出现结果不一致情况,这里我们可以通过Java内存模型的图解来更直观的看到这个情况是怎么出现的。
图中线程1的工作内存和线程2的工作内存就是我们上面描述的当有多个线程操作一个变量时,每个线程就会将变量复制一份到自己的工作内存当中。当我们的多线程执行的时候,每一个线程赋值一份变量,都对值进行修改,当共享变量不可见的时候,最终就会导致结果不一致。
并发编程的三个重要特性
- 原子性
- 原型性是指一个操作的完整性,要么该操作改变的值或者资源全部成功,要么全部不成功。
- 有序性
- 所谓有序性就是指代码在执行过程当中的先后顺序。
- 可见性
- 可见性在我们最上面的例子里面就展现了,就是一个线程修改共享变量的值的时候,另外一个线程能够看到这个变量的值被改变。
在我们多线程并发编程当中,它的三大特性是保证并发执行不出现错误的关键,volatile我们目前能够看到在并发编程当中能够保证可见性。除了可见性外还其实它还可以保证有序性,只是不能保证原子性而已。假若能够保证原子性,它和synchronize的作用基本那就是一样的,只是底层的实现原理不一样而已。
volatile如何保证有序性(摘抄)
volatile关键字对顺序性的保证就比较霸道,直接禁止JVM和处理器对volatile关键字修饰的指令重新排序,但是对于volatile前后无依赖关系的指令则可以随便怎么排序。
volatile可见性的底层实现原理
volatile底层的实现其实是通过lock关键字进行实现的,我们可以去获取class的汇编码,当使用volatile修饰和不使用volatile的代码分别获取到class的汇编码,然后进行对比,你会发现标有volatile的变量在进行写操作时,会在前面加上lock质量前缀。而lock指令前缀会做如下两件事
- 将当前处理器缓存行的数据写回到内存。lock指令前缀在执行指令的期间,会产生一个lock信号,lock信号会保证在该信号期间会独占任何共享内存。lock信号一般不锁总线,而是锁缓存。因为锁总线的开销会很大。
- 将缓存行的数据写回到内存的操作会使得其他CPU缓存了该地址的数据无效。
volatile和synchronize的区别
- volatile只能修饰实例变量或者类变量,synchronize只能修饰方法或者语句块
- volatile无法保证原子性,synchronize能够保证原子性
- volatile和synchronize都能保证有序性,只是实现方式不一样
- volatile不会使线程陷入阻塞,synchronize相反
总结
volatile被称为轻量级的synchronize是因为他能够有效的实现并发编程的有序性和可见性。但是同时它有自己的缺点,比如不能保证原子性的问题。部分场景能够直接volatile,比如对线程的唤起和关闭。synchronize虽然能够保证并发编程有点三要素,但是会造成线程阻塞。
volatile关键字的详解-并发编程的体现的更多相关文章
- 【Java并发编程】6、volatile关键字解析&内存模型&并发编程中三概念
volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...
- volatile关键字解析&内存模型&并发编程中三概念
原文链接: http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java5之前,它是一个 ...
- AQS详解,并发编程的半壁江山
千呼万唤始出来,终于写到AQS这个一章了,其实为了写这一章,前面也是做了很多的铺垫,比如之前的 深度理解volatile关键字 线程之间的协作(等待通知模式) JUC 常用4大并发工具类 CAS 原子 ...
- 剑指Offer——线程同步volatile与synchronized详解
(转)Java面试--线程同步volatile与synchronized详解 0. 前言 面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现 ...
- ava下static关键字用法详解
Java下static关键字用法详解 本文章介绍了java下static关键字的用法,大部分内容摘自原作者,在此学习并分享给大家. Static关键字可以修饰什么? 从以下测试可以看出, static ...
- [转]Hadoop集群_WordCount运行详解--MapReduce编程模型
Hadoop集群_WordCount运行详解--MapReduce编程模型 下面这篇文章写得非常好,有利于初学mapreduce的入门 http://www.nosqldb.cn/1369099810 ...
- 详解Python编程中基本的数学计算使用
详解Python编程中基本的数学计算使用 在Python中,对数的规定比较简单,基本在小学数学水平即可理解. 那么,做为零基础学习这,也就从计算小学数学题目开始吧.因为从这里开始,数学的基础知识列位肯 ...
- Java并发编程1--synchronized关键字用法详解
1.synchronized的作用 首先synchronized可以修饰方法或代码块,可以保证同一时刻只有一个线程可以执行这个方法或代码块,从而达到同步的效果,同时可以保证共享变量的内存可见性 2.s ...
- Java并发——线程同步Volatile与Synchronized详解
0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...
随机推荐
- 数字IC前后端设计中的时序收敛(六)--Max Fanout违反
本文转自:自己的微信公众号<数字集成电路设计及EDA教程>(二维码见博文底部) 里面主要讲解数字IC前端.后端.DFT.低功耗设计以及验证等相关知识,并且讲解了其中用到的各种EDA工具的教 ...
- 个人永久性免费-Excel催化剂功能第97波-快递单号批量查询物流信息
电商时代,快递已进千万家,做电商零售行业的,快递信息的再挖掘,也显得更有意义,是数据精细化运营中必不可少的一环.一般站在系统的角度,数据用于业务流转的增删改查使用,而对于分析需求来说,这些业务系统里集 ...
- Spark学习之RDD
RDD概述 什么是RDD RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变.可分区.里面的元素可并行计算的集合 ...
- C#3.0新增功能09 LINQ 标准查询运算符 01 概述
连载目录 [已更新最新开发文章,点击查看详细] 标准查询运算符 是组成 LINQ 模式的方法. 这些方法中的大多数都作用于序列:其中序列指其类型实现 IEnumerable<T> 接 ...
- Flink实战(七) - Time & Windows编程
0 相关源码 掌握Flink中三种常用的Time处理方式,掌握Flink中滚动窗口以及滑动窗口的使用,了解Flink中的watermark. Flink 在流处理工程中支持不同的时间概念. 1 处理时 ...
- JAVA项目从运维部署到项目开发(五. Nginx)
<Nginx与Nginx-rtmp-module搭建RTMP视频直播和点播服务器>一文简单介绍了关于直播数据流的nginx相关配置,下面简单介绍下各种项目如何配置nginx. web项目. ...
- java并发笔记之证明 synchronized锁 是否真实存在
警告⚠️:本文耗时很长,先做好心理准备 证明:偏向锁.轻量级锁.重量级锁真实存在 由[java并发笔记之java线程模型]链接: https://www.cnblogs.com/yuhangwang/ ...
- 7kyu (难度系数kyu阶段数值越大难度越低) 数组分组及求和
几个人排成一排,分成两队.第一个人进入一队,第二个人进入第二队,第三个人进入第一队,以此类推. 给定一个正整数的数组(人的权重),返回两个整数的新数组/元组,其中第一个是第1组的总重量,第二个是第2组 ...
- 浏览器如何加载和解析CSS——CSS样式来源与层叠规则
关于CSS样式首先得理解浏览器如何加载它们,最终的页面样式是如何呈现的? CSS层叠样式表的关键在于"层叠",会根据选择符的使用而将样式相互叠加或者覆盖. CSS样式表之所有有&q ...
- 2019最新idea注册码
2019最新注册码到2020年1月7号 N757JE0KCT-eyJsaWNlbnNlSWQiOiJONzU3SkUwS0NUIiwibGljZW5zZWVOYW1lIjoid3UgYW5qdW4iL ...