再有人问你volatile是什么,把这篇文章也发给他
在上一篇文章中,我们围绕volatile关键字做了很多阐述,主要介绍了volatile的用法、原理以及特性。在上一篇文章中,我提到过:volatile只能保证可见性和有序性,无法保证原子性。关于这部分内容,有读者阅读之后表示还是不是很理解,所以我再单独写一篇文章深入分析一下。
volatile与有序性
在上一篇文章中我们提到过:volatile一个强大的功能,那就是他可以禁止指令重排优化。通过禁止指令重排优化,就可以保证代码程序会严格按照代码的先后顺序执行。那么volatile又是如何禁止指令重排的呢?
先给出结论:volatile是通过内存屏障来来禁止指令重排的。
内存屏障(Memory Barrier)是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。下表描述了和volatile有关的指令重排禁止行为:
从上表我们可以看出:
当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。
当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。
当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。
具体实现方式是在编译期生成字节码时,会在指令序列中增加内存屏障来保证,下面是基于保守策略的JMM内存屏障插入策略:
- 在每个volatile写操作的前面插入一个
StoreStore
屏障。- 对于这样的语句Store1;
StoreStore
; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
- 对于这样的语句Store1;
- 在每个volatile写操作的后面插入一个
StoreLoad
屏障。- 对于这样的语句Store1;
StoreLoad
; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
- 对于这样的语句Store1;
- 在每个volatile读操作的后面插入一个
LoadLoad
屏障。- 对于这样的语句Load1;
LoadLoad
; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
- 对于这样的语句Load1;
- 在每个volatile读操作的后面插入一个
LoadStore
屏障。- 对于这样的语句Load1;
LoadStore
; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
- 对于这样的语句Load1;
所以,volatile通过在volatile变量的操作前后插入内存屏障的方式,来禁止指令重排,进而保证多线程情况下对共享变量的有序性。
volatile与可见性
在上一篇文章中我们提到过:Java中的volatile
关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。
其实,volatile对于可见性的实现,内存屏障也起着至关重要的作用。因为内存屏障相当于一个数据同步点,他要保证在这个同步点之后的读写操作必须在这个点之前的读写操作都执行完之后才可以执行。并且在遇到内存屏障的时候,缓存数据会和主存进行同步,或者把缓存数据写入主存、或者从主存把数据读取到缓存。
我们在内存模型是怎么解决缓存一致性问题的?一文中介绍过缓存缓存一致性协议,同时也提到过内存一致性模型的实现可以通过缓存一致性协议来实现。同时,留了一个问题:已经有了缓存一致性协议,为什么还需要volatile?
这个问题的答案可以从多个方面来回答:
1、并不是所有的硬件架构都提供了相同的一致性保证,Java作为一门跨平台语言,JVM需要提供一个统一的语义。
2、操作系统中的缓存和JVM中线程的本地内存并不是一回事,通常我们可以认为:MESI可以解决缓存层面的可见性问题。使用volatile关键字,可以解决JVM层面的可见性问题。
3、缓存可见性问题的延伸:由于传统的MESI协议的执行成本比较大。所以CPU通过Store Buffer和Invalidate Queue组件来解决,但是由于这两个组件的引入,也导致缓存和主存之间的通信并不是实时的。也就是说,缓存一致性模型只能保证缓存变更可以保证其他缓存也跟着改变,但是不能保证立刻、马上执行。
- 其实,在计算机内存模型中,也是使用内存屏障来解决缓存的可见性问题的(再次强调:缓存可见性和并发编程中的可见性可以互相类比,但是他们并不是一回事儿)。写内存屏障(Store Memory Barrier)可以促使处理器将当前store buffer(存储缓存)的值写回主存。读内存屏障(Load Memory Barrier)可以促使处理器处理invalidate queue(失效队列)。进而避免由于Store Buffer和Invalidate Queue的非实时性带来的问题。
所以,内存屏障也是保证可见性的重要手段,操作系统通过内存屏障保证缓存间的可见性,JVM通过给volatile变量加入内存屏障保证线程之间的可见性。
内存屏障
再来总结一下Java中的内存屏障:用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
volatile与原子性
在以前的文章中,我们介绍synchronized
的时候,提到过,为了保证原子性,需要通过字节码指令monitorenter
和monitorexit
,但是volatile
和这两个指令之间是没有任何关系的。volatile
是不能保证原子性的。
网上有很多文章,拿i++
的例子说明volatile
不能保证原子性,然后进行各种分析,有的说由于引入内存屏障导致无法保证原子性,有的说一段i++
代码,在编译后字节码为:
10: getfield #2 // Field i:I
14: iconst_1
15: iadd
16: putfield #2 // Field i:I
在不考虑内存屏障的情况下,一个i++
指令也包含了四个步骤。
这些分析,只是说明了i++
本身并不是一个原子操作,即使使用volatile
修饰i
,也无法保证他是一个原子操作。并不能解释为什么volatile
为啥不能保证原子性。
要我说,由于CPU按照时间片来进行线程调度的,只要是包含多个步骤的操作的执行,天然就是无法保证原子性的。因为这种线程执行,又不像数据库一样可以回滚。如果一个线程要执行的步骤有5步,执行完3步就失去了CPU了,失去后就可能再也不会被调度,这怎么可能保证原子性呢。
为什么synchronized
可以保证原子性 ,因为被synchronized
修饰的代码片段,在进入之前加了锁,只要他没执行完,其他线程是无法获得锁执行这段代码片段的,就可以保证他内部的代码可以全部被执行。进而保证原子性。
但是synchronized
对原子性保证也不绝对,如果真要较真的话,一旦代码运行异常,也没办法回滚。所以呢,在并发编程中,原子性的定义不应该和事务中的原子性一样。他应该定义为:一段代码,或者一个变量的操作,在没有执行完之前,不能被其他线程执行。
那么,为什么volatile
不能保证原子性呢?因为他不是锁,他没做任何可以保证原子性的处理。当然就不能保证原子性了。
总结
本文在上一篇文章的基础上,再次介绍了volatile和原子性、有序性以及可见性之间的关系。有序性和可见性是通过内存屏障实现的。而volatile是无法保证原子性的。
参考资料
from: https://www.hollischuang.com/archives/2673
再有人问你volatile是什么,把这篇文章也发给他的更多相关文章
- 再有人问你synchronized是什么,就把这篇文章发给他
在再有人问你Java内存模型是什么,就把这篇文章发给他.中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronize ...
- 再有人问你Java内存模型是什么,就把这篇文章发给他
前几天,发了一篇文章,介绍了一下JVM内存结构.Java内存模型以及Java对象模型之间的区别.有很多小伙伴反馈希望可以深入的讲解下每个知识点.Java内存模型,是这三个知识点当中最晦涩难懂的一个,而 ...
- 再有人问你HashMap,把这篇文章甩给他!
声明:本文以jdk1.8为主! 搞定HashMap 作为一个Java从业者,面试的时候肯定会被问到过HashMap,因为对于HashMap来说,可以说是Java==集合中的精髓==了,如果你觉得自己对 ...
- 再有人问你HashMap,把这篇文章甩给他
搞定HashMap 作为一个Java从业者,面试的时候肯定会被问到过HashMap,因为对于HashMap来说,可以说是Java==集合中的精髓==了,如果你觉得自己对它掌握的还不够好,我想今天这篇文 ...
- [转] 以后再有人问你selenium是什么,你就把这篇文章给他
本文转自:https://blog.csdn.net/TestingGDR/article/details/81950593 写在最前面:目前自动化测试并不属于新鲜的事物,或者说自动化测试的各种方法论 ...
- 【转】再有人问你Http协议是什么,把这篇文章发给他
一.HTTP简介 1.HTTP协议,即超文本传输协议(Hypertext transfer protocol).是一种详细规定了浏览器和万维网(WWW = World Wide Web)服务器之间互相 ...
- 【Redis数据库】再有人问你CAP理论是什么,就把这篇文章发给他
CAP是Consistency(一致性),Availability(可用性),Partition tolerance(分区容错性)的缩写.在学习redis过程中看到这个名词,查找各位大佬的文章发现这篇 ...
- 关于volatile 最完整的一篇文章
你真的了解volatile关键字吗? 一.Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每 ...
- 面试阿里,腾讯90%会被问到的zookeeper,把这篇文章看完就够了。
Zookeeper概述 zookeeper高容错数据一致性协议(CP)的分布式小文件系统,提供类似于文件系统的目录方式的数据存储. 全局数据一致性:每个server保存一份相同的数据副本,client ...
随机推荐
- 【BZOJ】4561: [JLoi2016]圆的异或并
题解 我们把圆拆成两个圆弧,按照圆弧的左右端点排序来增加和删除 那么我们把圆弧按照纵坐标排序,一定是两两不相交的 我们新加入一个圆的时候,找上圆弧的前驱,如果前驱是一个上圆弧,那么这个上圆弧所在的圆就 ...
- ubuntu14.0安装ITK的步骤
(1) sudo apt-get install cmake (2) sudo apt-get install cmake-curses-gui (3)下载安装包InsightToolkit-4.11 ...
- ClouderaManager中Event Server报No such file or directory
错误日志如下: 2015-06-24 06:13:10,176 ERROR com.cloudera.cmf.eventcatcher.server.EventCatcherService: Erro ...
- win7下再装Ubuntu双系统
一.UltraISO制作U盘启动盘 1.1打开 UltraISO,单机“文件”,选择“打开”. 1.2然后单击“启动”,选择“写入硬盘映像”. 二.装Ubuntu 前面省略,直接到安装类型(我的安装好 ...
- odoo视图继承
odoo视图继承 简介继承 在odoo里可以通过继承的方式来改写已经存在的view 对比view的定义视图定义中写法多了inherit_id inherit_id指向要改写的view <fiel ...
- 用户组修改工具samusrgrp
用户组修改工具samusrgrp Windows系统内置了很多用户组,如Administrators.PowerUser.User等.用户隶属不同的组,就具备对应的权限.Kali Linux提供一 ...
- 领英Linkedin信息搜集工具InSpy
领英Linkedin信息搜集工具InSpy 领英Linkedin是一个知名职业社交媒体网站.通过该网站,渗透测试人员可以获取公司内部组成和员工信息.Kali Linux提供一款专用的信息收集工具I ...
- Python基础笔记(三)
1. 循环与流程控制 (1) for myList1 = ["A", "B", "C", "D"] # 正序遍历 for ...
- SNOI 滚粗记
连睡觉都只能睡一半就吓醒 真的蠢 CE了四道 没有cstring 踏马本机怎么能过??!! 还有几次夏令营什么的 可能水水就结束了 最单纯的拿点优惠的想法也没实现 都说以后会有用的 大概是吧 也大概是 ...
- Codeforces Round #258 (Div. 2) E. Devu and Flowers 容斥
E. Devu and Flowers 题目连接: http://codeforces.com/contest/451/problem/E Description Devu wants to deco ...