干货:Java并发编程系列之volatile(二)
接上一篇《Java并发编程系列之synchronized(一)》,这是第二篇,说的是关于并发编程的volatile元素。
Java语言规范第三版中对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。
java 内存模型的核心是围绕着在并发过程中如何处理原子性、可见性、有序性这3个特性来展开的,它们是多线程编程的核心。
- 原子性(Atomicity):是指一个操作是不可中断的,即使是多个线程同时执行的情况下,一个操作一旦开始,就不会被其它线程干扰。对于基本类型的读写操作基本都具有原子性的(在32位操作系统中 long 和 double 类型数据的读写不是原子性的,因为它们有64位)。
- 可见性(Visibility):是指在多线程环境下,当一个线程修改了某一个共享变量的值,其它线程能够立刻知道这个修改。
- 有序性(Ordering):是指程序的执行顺序是按照代码的先后顺序执行的;对于这句话如果在单线程中所有的操作都是有序的,但是在多线程环境下,一个线程的操作相对于另外一个线程的操作是无序的。
了解volatile关键字之前需要先了解下Java内存模型,java内存模型抽象示意图如下:
Java内存模型
线程A和线程B之间若要通信的话, 必须经历下面两个步骤 :
(1)线程A和线程A本地内存中更新过的共享变量刷新到主存中去。
(2)线程B到主存中去读取线程A之前更新过的共享变量。
由此可见执行下面的语句:
int a = 100 线程必须现在自己的工作线程中对变量i所在的缓存进行赋值操作,然后再写入主存当中,而不是直接将数值100写入主存中。
特性
可见性 当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存,所以对其他线程是可见的。当其他线程需要读取该值时,其他线程会去主存中读取新值。相反普通的共享变量不能保证可见性,因为普通共享变量被修改后并不会立即被写入主存,何时被写入主存也不确定。当其他线程去读取该值时,此时主存可能还是原来的旧值,这样就无法保证可见性。
有序性 java内存模型中允许编译器和处理器对指令进行重排序,虽然重排序过程不会影响到单线程 执行的正确性,但是会影响到多线程并发执行的正确性。这时可以通过volatile来保证有序性,除了volatile,也可以通过synchronized和Lock来保证有序性。synchronized和Lock保证每个时刻只有一个线程执行同步代码,这相当于让线程顺序执行同步代码,从而保证了有序性。如果不考虑原子性操作的话volatile比synchronized和Lock更轻量级,成本更低。
不保障原子性 volatile关键字只能保证共享变量的可见性和有序性。如果volatile修饰并发线程中共享变量, 而该共享变量是非原子操作的话,并发中就会出现问题。比如下面代码:
publicclassHelloVolatile{publicvolatileintmNumber =0;publicstaticvoidmain(String []args){ final HelloVolatile hello =newHelloVolatile();for(inti =0; i<10; i++){newThread(){publicvoidrun(){for(intj =0; j<1000; j++){ hello.mNumber ++; } } }.start(); }while(Thread.activeCount()>2){ Thread.yield(); } System.out.println("number:"+hello.mNumber); }}
这段代码预期结果是10000,可是每次执行结果都有可能不一样。这是因为自增或自减都是非原子操作。
(1) 假如mNumber此时等于100,线程1进行自增操作。
(2)线程1先读取了mNumber的值100,然后它被堵塞了。
(3)这时候线程2读取mNumber的值100,然后进行了自增操作,并写入到主存中, 这时候主存中的值为101。
(4)这时候线程1继续执行,因为此前线程1已经读取到值100,然后进行自增操作101,然后将101写入到主存中。
可以看到两个线程分别对100进行了+1操作,预期主存中的nNumber = 102,实际mNumebr = 101; 这就是因为非原子操作造成的。
使用场景
(1)并发编程中不依赖于程序中任意其状态的状态标识。可以通过关键字volatile代替synchronized, 提高程序执行效率,并简化代码。
(2)单例模式的双重检查模式DCL
publicclassDclSingleton{privatevolatilestaticDclSingleton mInstance =null;publicstaticDclSingletongetInstance(){if(mInstance==null){synchronized(DclSingleton.class){if(mInstance==null){ mInstance =newDclSingleton(); } } }returnmInstance; }}
原理浅析
将volatile修饰的变量转变成汇编代码,如下:
... lock addl $0x0,(%rsp)
通过查IA-32架构安全手册可知,Lock前缀指令在多核处理器会引发两件事。
1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
解读 :
为了提高,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作完不知道何时再写回内存。如果对声明了volatile的变量进行写操作,JVM会向处理机发送一条Lock前缀指令,将这个变量所在的缓存行的数据写回到系统内存。
但是写会内存后,如果其他处理器缓存的值还是旧的,再执行计算操作就会出现问题。所以在多处理器下,为了保证各个处理器缓存是一致的,就会实现缓存一致性协议,如下图:
每个处理器通过嗅探在总线上传播的数据来检查自己缓存的数据是否过期了,当处理器发现自己的缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态。当处理器对这个数据进行操作的时候,就会重新从系统内存中把数据读到处理器缓存中。
文末福利:
想要了解更多并发编程知识点的,可以关注我一下,我后续也会整理更多关于并发编程这一块的知识点分享出来,另外顺便给大家推荐一个交流学习群:650385180,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化以及并发编程这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多,以下学习资源都在群的共享区。
转载于:https://www.cnblogs.com/lfs2640666960/p/8807751.html
干货:Java并发编程系列之volatile(二)相关推荐
- Java并发编程系列
Java并发编程系列 2018-03-08 Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) ...
- # Java 并发编程的艺术(二)
Java 并发编程的艺术(二) 文章目录 Java 并发编程的艺术(二) 并发编程的挑战 上下文切换 如何减少上下文的切换 死锁 资源限制的挑战 Java 并发机制的底层实现原理 volatile 的 ...
- 并发编程系列之volatile关键字详解
并发编程系列之volatile关键字详解 1.volatile是什么? 首先简单说一下,volatile是什么?volatile是Java中的一个关键字,也是一种同步机制.volatile为了保证变量 ...
- java并发实战编程pdf_「原创」Java并发编程系列25 | 交换器Exchanger
2020年Java面试题库连载中 [000期]Java最全面试题库思维导图 [001期]JavaSE面试题(一):面向对象 [002期]JavaSE面试题(二):基本数据类型与访问修饰符 [003期] ...
- Java 并发编程系列之带你了解多线程
早期的计算机不包含操作系统,它们从头到尾执行一个程序,这个程序可以访问计算机中的所有资源.在这种情况下,每次都只能运行一个程序,对于昂贵的计算机资源来说是一种严重的浪费. 操作系统出现后,计算机可以运 ...
- reentrantlock非公平锁不会随机挂起线程?_【原创】Java并发编程系列16 | 公平锁与非公平锁...
本文为何适原创并发编程系列第 16 篇,文末有本系列文章汇总. 上一篇提到重入锁 ReentrantLock 支持两种锁,公平锁与非公平锁.那么这篇文章就来介绍一下公平锁与非公平锁. 为什么需要公平锁 ...
- Java 并发编程系列之闭锁(CountDownLatch)
在讲闭锁之前,我们先来思考一个问题:在多线程环境下,主线程打印一句话,如何保证这句话最后(其他线程全部执行完毕)打印? 博主目前可以想到的实现方式有两种.一种是通过 join() 方法实现,另一种就是 ...
- Java 并发编程CAS、volatile、synchronized原理详解
CAS(CompareAndSwap) 什么是CAS? 在Java中调用的是Unsafe的如下方法来CAS修改对象int属性的值(借助C来调用CPU底层指令实现的): /*** * @param o ...
- 『图解Java并发编程系列』10张图告诉你Java并发多线程那些破事
目录 线程安全问题 活跃性问题 性能问题 有态度的总结 头发很多的程序员:『师父,这个批量处理接口太慢了,有什么办法可以优化?』架构师:『试试使用多线程优化』第二天头发很多的程序员:『师父,我已经使用 ...
- 『死磕Java并发编程系列』并发编程工具类之CountDownLatch
<死磕 Java 并发编程>系列连载中,大家可以关注一波:
最新文章
- asp.net中DataGrid性能测试
- androidinclude作用
- 【论文阅读】Triple GANs论文阅读
- Java时间日期格式转换
- Spring--IoC(2)
- cricheditview实现语法高亮和行号_Markdown语法详解及工具介绍
- JFace中TableViewer的使用
- Artifactory安装配置
- sony笔记本触摸板角落轻敲功能
- java的编译路径在哪_如何知道我的java编译器的路径
- 报童问题详细推导及利用Python的SAA方法求解
- freeMarker(四)——模板开发指南之模板
- QC新旧七图汇总连载10——树状图
- AI-大型软件研发效能倍增的银弹
- 互联网金融牌照有哪些?金融牌照一览表
- 除了经典怀旧服,还有什么能重新激活《魔兽世界》
- 一些浏览器HACKS
- 使用 font-spider 对 webfont 网页字体进行压缩
- 使用云效 修改 layui 环境变量
- 你可能还没听过DNA存储技术,但微软已经打算三年内将其商用了
热门文章
- c语言中ox1小于小于a,丹江口市2018适应性数学试卷和答案
- javascrit 数组方法总结(数组对象、栈,队列、重排序、操作数组方法、位置方法、归并方法、迭代方法)
- L3-014 周游世界 (30分)
- Eclipse、STS 常用设置、操作 与 常用快捷键
- LayaAir UI 组件 # CheckBox 复选框
- HTTP Header 详解 Requests 与 Responses 头信息
- mysql 查询递归自身,mysql 递归查新
- java iterator 源码_Java 集合系列(四)—— ListIterator 源码分析
- 阶段5 3.微服务项目【学成在线】_day04 页面静态化_23-页面预览-页面预览开发
- 阶段3 3.SpringMVC·_07.SSM整合案例_01.ssm整合说明