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: ...
随机推荐
- 将n个东西分成n1,n2,n3,n4,....nr 共 r组分给r个人有多少种分法。
(n!/(n1! *n2! *n3!..nr!) ) * r!/( 同数量组A的数量! 同数量组B的数量!....) 比方20个东西分成2,2,,2,2 3,3,3,3 8组分给8个人有多少种 ...
- Effective Java 【考虑实现Comparable接口】
Effective Java --Comparable接口 compareTo方法是Comparable接口的唯一方法.类实现了Comparable接口,表明它的实例具有内在的排序关系. 自己实现co ...
- [Java算法] -- 1. 常用排序之冒泡排序和选择排序
使用Java语言实现冒泡排序和选择排序 推荐一个数据结构可视化的网站:http://zh.visualgo.net/zh (暂时访问不了) 对排序不太熟悉的朋友,建议去上面的网站学习一下,你将会发现一 ...
- hbase单机版安装+phoneix SQL on hbase 单节点安装
hbase 单机安装部署及phoneix 单机安装 Hbase 下载 (需先配置jdk) https://www.apache.org/dyn/closer.lua/hbase/2.0.1/hbase ...
- ubuntu安装spyder和jupyter notebook
ubuntu安装spyder和jupyter notebook 安装spyder 安装spyder sudo apt install spyder sudo apt install spyder3 安 ...
- ICO图标下载地址
http://findicons.com/ http://www.iconfont.cn/
- Android中实现gif动画
一.需求 Android本身没有提供直接显示gif动画的相关控件,因此需要自定义GifImageView类来实现gif的播放,主要是使用的Movie类来解决的. 二.自定义GifImageView p ...
- Codeforces Round #418 (Div. 2)
A: 不细心WA了好多次 题意:给你一个a序列,再给你个b序列,你需要用b序列中的数字去替换a序列中的0,如果能够替换,则需要判断a是否能构成一个非递增的序列,a,b中所有的数字不会重复 思路:就是一 ...
- Android-Nexus5-命令刷机
第一步)需要有有一部Nexus5手机: 第二步)寻找 .tgz 刷机包: 1: 2: 3.进行hammerhead-lmy47d-factory-6c1ad81e.tgz的下载: 4 进行解压: 5. ...
- git学习笔记 看廖大神视频小记
1.创建一个空目录 $ mkdir gittemp $cd gittemp $pwd //x显示当前目录 2.$ git init 把这个目录变成git可以管理的仓库 多的一个隐藏的.git 目录 可 ...