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: ...
随机推荐
- VS2017离线安装入门与出家
重做系统,并且VS2017也发布有一段时间了,可以试试了. 于是网上搜了下,离线安装要下载他的安装工具. https://www.visualstudio.com/zh-hans/downloads/ ...
- java 项目相关 学习记录
一位资深程序员大牛给予Java初学者的学习路线建议 [任何时期都可以好好看看] https://www.imooc.com/article/8993 https://www.jianshu.com/ ...
- String StringBuilder StringBuffer区别
String StringBuilder StringBuffer String类是final类,不可以被继承,且它的成员方法也是final方法,当一个字符串对象进行操作操作时,任何的改变不会影响到这 ...
- Apache Tomcat Eclipse Integration
An Illustrated Quick Start Guide Apache Tomcat makes hosting your applications easy. The Eclipse IDE ...
- easyUI分页实现加搜索功能
前台页面: js代码: ps:pagination为true时会在table下面加上easyUI的分页. load函数会将查询值传给datagrid并传给后台重新加载. DAO.xml为: 后台代码实 ...
- Express实例代码分析1——简单的用户验证登录文件
/** * Module dependencies. */ var express = require('../..');// ../..是上级目录的上级目录 var hash = require(' ...
- stm32模拟IO读写AT24C02
/* *@brief 主机向从机写多字节 * *@param addr - 地址 *@param p_buf - 数据指针 *@param len - 待写入字节长度 * *@return * *@n ...
- git安装以及初始化
安装文档参见:https://www.cnblogs.com/ximiaomiao/p/7140456.html 注意:安装成功后,用cmd进行基本信息设置时,当出现“git不是内部或外部命令,也不是 ...
- Centos6.5安装中文支持和中文输入法---VIM编辑器中文支持
Centos6.5安装中文支持和中文输入法 第一步:中文支持: 在shell命令下输入: # vi /etc/sysconfig/i18n 然后修改LANG="en_US.UTF-8 ...
- 数值计算 的bug:(理论)数学上等价,实际运行未必等价
1. 计算表达式的值(lambda 表达式) fun1 和 fun2 理论上是等价的:同样的输入情形下,两种输出结果不一致. # fun1 定义 fun1=lambda x:sqrt(x+1)-sqr ...