java基础系列--volatile关键字
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/7833881.html
1、volatile简述
据说,volatile是java语言中最轻量级的并发控制方式。
volatile可以实现可见性、有序性,但是无法实现原子性,相对来说:synchronized可以实现这三个并发特性,所以我们可以使用synchronized来代替volatile,但是一直以来synchronized都已重量级闻名,其实在jdk1.5之后的版本中,java对synchronized进行了针对性优化处理,其操作速度已经不再是制约其是否可选择的因素,现在一般通过实际的情况来决定使用哪种方式。
2、volatile特性
2.1 可见性
volatile可以保证变量的可见性,指的是什么呢?
可见性指的是在某一线程中对变量进行修改之后,其他线程可以立即发现并使用这个修改(一个线程的修改对其他线程可见)。
可见性的实现方式:volatile通过对java内存模型中主内存和工作内存交互方式的控制来实现。volatile确保一个线程对其修饰的变量的更改立即写入到主内存,同时确保每一次针对其修饰变量的读取操作直接从主内存中获取(即volatile强制将assign赋值操作和store、write操作绑定在一起,将use使用操作强制和read、load操作绑定在一起,这样assign之后必须执行store、write操作,use操作之前必须先执行read、load操作)这样就确保了其他线程读取到的变量的值是最新的(不熟悉这几个操作的同学请先了解java内存模型)。
Volatile更底层的实现方式:一个线程对volatile变量进行了修改之后,会写到工作内存,这一步映射到底层就是cpu将计算结果保存到高速缓存中,这时会触发一个LOCK指令,这个指令有两个作用,第一,它会锁定总线或者缓存,将修改后的新值保存到系统内存中,映射到JVM就是保存到主内存中。第二,它会将其他CPU的高速缓存中的这个变量的值置为无效,映射到JVM就是讲其他线程的工作内存中保存的这个变量值置为无效,这样在这些其他线程要对变量进行操作时,读取变量时发现工作内存中的值是无效的,随即从主内存重新读取,并保存到工作内存。
这里还要说说明一点,无论是主内存还是工作内存(系统内存还是高速缓存)都只是保存数据的部件或位置,所有针对变量的操作全部需要在CPU中进行,所以即使将数据从主内存(系统内存)读取到工作内存(高速缓存)中之后,想要操作,还需要从工作内存(高速缓存)中读取到CPU中的寄存器中进行计算。
2.2 非原子性
注意:volatile可以实现可见性,但是无法实现原子性。单个volatile变量的读写操作具有原子性,但是复合操作是与java代码相关的,并不是volatile这么一个关键字既可以控制得了的。(请将可见性和原子操作区分开来,我之前就混淆在一起,分开之后立即通透了)
java中实现原子操作的方式还是有很多的,但是并不包含volatile,简单的实现方式有:atomic包下的原子操作(通过CAS实现),基本数据类型的读写操作,加锁(synchronized或者Lock)实现等。
java中经典的非原子操作如自增实现,普通的i++操作看似只有一句话,但是编译成机器指令之后拥有多少行,不可知,肯定不是一句就能实现的,这么多命令要执行当然无法保证原子性,这时候我们可以用AtomicInteger和AtomicLong原子操作类的getAndIncrement()方法来实现,当然是用synchronized加锁同样可以实现。
2.3 有序性
volatile的另一个作用就是避免重排序优化,使用内存屏障的方式来实现禁止重排序优化。在单线程环境中当然没有必要禁止重排序优化,但是在多线程环境中指令重排序后执行就可能会出错,比如线程A中需要检测线程B中的某一个变量的值,依据这个值来进行某些操作。如果没有使用volatile修饰该变量,线程B中针对这个变量的操作就可能会发生重排序,可能会提前执行,这时一旦操作提前执行,那么线程A就可以会提前得到这个变量的值(或许是在一些线程A的准备工作还未全部准备好的情况,假设这些准备工作在线程B中定义,但是与变量操作无依赖关系,一旦变量操作提前,这些准备工作就会滞后,这时线程A就会在准备工作尚未完成的情况下启动执行后行代码,导致出错)。为变量加上volatile修饰之后,就会禁止其操作的指令重排序优化,保证所以的准备工作全部执行完成之后再进行变量操作,然后线程A在准备齐备的情况下启动,得以正常执行。
volatile有序性的实现原理是什么呢?
volatile底层通过内存屏障的方式来禁止重排序优化,具体来说,JMM采用的是保守策略,所谓保守策略,就是通过冗余的内存屏障来保证所有影响Volatile功能的重排序全部被禁止,保证volatile功能的完整性,此处冗余的意思就是,可能会存在多余的内存屏障,但这种多余的内存屏障是不影响操作执行的,或者说是有的内存屏障所禁止的重排序操作如果实际发生了重排序也不会影响操作结果的情况,但是这种冗余的内存屏障可以排除那种任何特殊情况来确保volatile功能的完整性。
内存屏障包括:
(1)volatile变量写操作之前的storestore屏障,这个屏障保证所有在volatile写操作之前的任何操作都不能被重排序到volatile写操作之后,确保volatile变量写操作的正确性,因为所有直接或间接的修改都在写操作之前完成了,那么写的变量值一定是最终的正确值。
(2)volatile变量写操作之后的storeload屏障,这个屏障保证所有在volatile写操作之后的任何操作都不能被重排序到volatile写操作之前,确保volatile变量写操作的正确性,其实这里真正禁止的是其后可能出现的volatile读写操作被重排序到这个写操作之前。
(3)volatile变量读操作之后的loadload屏障,这个屏障保证所有在volatile读操作之后的任何读操作都不会被重排序到volatile读操作之前,确保volatile变量读操作的正确性。
(4)volatile变量读操作之后的loadstore屏障,这个屏障保证所有在volatile读操作之后的任何写操作都不会被重排序到volatile读操作之前,确保volatile变量读操作的正确性。
上面的内容并不好理解和记忆,我们可以总结如下:
所有Volatile写操作之前的操作禁止重排序到该写操作之后;
所有volatile读操作之后的操作禁止重排序到该读操作之前;
volatile写操作之后是volatile读操作时,禁止重排序。
记住上述三点就可以了!
3、volatile使用
在涉及到并发操作的情况下,可以优先考虑是否可以使用volatile来解决问题,适合的场景如下:
在只涉及可见性,针对变量的操作只是简单的读写(保证操作的原子性)的情况下可以使用volatile来解决高并发问题,如果这时针对变量的操作是非原子的操作,这时如果只是简单的i++式的操作,可以使用原子类atomic类来保证操作的原子性(采用CAS实现),如果是复杂的业务操作,那么舍弃volatile,采用锁来解决并发问题(synchronized或者Lock)。
4、volatile总结
volatile可以实现可见性和有序性,无法实现原子性 ,简单的原子操作可以委托给其他方式进行实现,复杂的原子操作需要借助锁来实现,这时候完全没有必要加上volatile了,因为锁已经包含了volatile的功能。
java基础系列--volatile关键字的更多相关文章
- Java基础系列--static关键字
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/8477914.html 一.概述 static关键字是Java诸多关键字中较常使用的一个,从 ...
- Java基础系列--final关键字
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/8482909.html 一.概述 final是Java关键字中最常见之一,表示"最 ...
- Java基础系列--instanceof关键字
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/8492158.html instanceof关键字是在Java类中实现equals方法最常使 ...
- 夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理
目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 ...
- java并发系列(六)-----Java并发:volatile关键字解析
在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...
- 夯实Java基础系列9:深入理解Class类和Object类
目录 Java中Class类及用法 Class类原理 如何获得一个Class类对象 使用Class类的对象来生成目标类的实例 Object类 类构造器public Object(); register ...
- Java基础系列-equals方法和hashCode方法
原创文章,转载请标注出处:<Java基础系列-equals方法和hashCode方法> 概述 equals方法和hashCode方法都是有Object类定义的. publi ...
- Java基础-标识符与关键字
Java基础-标识符与关键字 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是标识符 标识符就是程序员在编写程序时,给类,变量,方法等起的名字. 二.标识符的命名规则 1& ...
- 夯实Java基础系列1:Java面向对象三大特性(基础篇)
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...
随机推荐
- vi中删除所有查找到的行
vi中删除所有查找到的行 在linux中查找文件,结果中有很多是.svn目录里的,把查找结果放到一个文件里. 用vi打开该文件,按ESC,进入命令行模式,输入 :g/\.svn/d 就可以把所有含”. ...
- eclipse 无法记住svn密码
每次要求输入密码,很恼人.经过一番折腾,在stackoverflow上找到了解决方案,上面大神果然多 简单的说,就是通过查看工作空间的日志文件,发现报了个java异常,缺少一个class文件(org. ...
- MVC实例应用
MVC是Model-View-Controller的简称,即模型-视图-控制器.MVC是一种设计模式, 它把应用程序分成三个核心模块:模型.视图.控制器,它们各自处理自己的任务. 1.模型(Model ...
- 画PCB
1.AD16Design中Boardshape没有redefine board shape选项来修改板子的形状:{ 解决办法:在英文输入模式下按“1”键整个界面会变绿,此时就会有redefine bo ...
- Python序列结构--字典
字典:反映对应关系的映射类型 字典(dict)是包含若干“键:值”元素的无序可变序列 字典中元素的“键”可以是python中任意不可变数据,例如整数.实数.复数.字符串.元组等类型可哈希数据,“键”不 ...
- Python爬取淘宝店铺和评论
1 安装开发需要的一些库 (1) 安装mysql 的驱动:在Windows上按win+r输入cmd打开命令行,输入命令pip install pymysql,回车即可. (2) 安装自动化测试的驱动s ...
- jQuery-事件命名空间
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- 导入Dynamic Web Project后程序有红叉但是可以运行
解决方法: 进入工程下的.setting文件夹,用记事本编辑org.eclipse.wst.common.project.facet.core.xml, 把<runtime name=" ...
- 关于Nginx设置端口号,在Asp.net 获取不到的,解决办法
不知道你有没有遇到过这样的问题,网站访客多起来后,心里很是高兴,加上了Nginx反向代理,出问题了 原来是这么写的: Request.Url.ToString() 输出是这样的: http://www ...
- weexpack打包weex项目运行/打包记录
构建weex项目 安装weex-toolkit cnpm install -g weex-toolkit 初始化一个项目只需新建文件夹并在目录下执行 weex init 即可 安装依赖:cnpm in ...