volatile足以保证数据同步吗
上一节已经了解了java内存模型,JMM为了提高执行性能,引入了工作内存和主存两个概念,在继续讨论之前必须先搞清四种存储介质:寄存器、高级缓存、RAM和ROM。
RAM与ROM大家都比较熟悉了,可以看成是我们经常说的内存与硬盘,寄存器属于处理器里面的一部分,而高级缓存cache是CPU设计者为提高性能引入的一个缓存,也可以说是属于处理器的一部分。在利用CPU进行运算时必定涉及操作数的读取,假如CPU直接读取ROM,那么这个读取速度简直是无法忍受的,于是引入了内存RAM,这样做确实让速度提高了很多,但由于CPU发展十分迅猛而另一方面RAM的发展受到技术及成本的限制发展缓慢,此时产生了一个很难调和的矛盾,CPU运算速度比从RAM读取数据的速度快了几个数量级,木桶原理我们都很熟悉了,桶的容量大小取决于最短的那块,这必将影响处理器的效率,于是又引入了高级缓存,直接在CPU添加了几个级别的缓存,他们的速度虽然无法与寄存器比较,但是速度已经提升很多,基本能跟CPU的计算速度相匹配。总结成一句话就是,为了解决CPU运算速度与读取速度的矛盾,引入了多级存储机制。
如图2-5-2-1所示,机器的四种存储介质是有关系的,一般程序运行时会将ROM相关的程序数据都读进RAM中,而需要运算的数据或运算过程中即将要用到的数据则会被读进高速缓存或寄存器中,假如要进行的运算所需要的所有数据及指令都在寄存器和高速缓存中,则这个运算过程则表现得非常平坦,并不存在性能瓶颈,因为运算速度跟读取速度基本匹配了。读取速度快慢的排序如下:寄存器>cache>RAM>ROM,用一个比较好理解但不完全正确的概念来解释,因为寄存器是离CPU最近的,所以读取最快,高速缓存次之,RAM第三,ROM离得最远,自然速度最慢(当然不能完全用距离来说明这个问题,但用距离是比较好理解的,另外的还因为这些存储介质的硬件设计不同、工作方式不同)。从另一个角度来看,CPU读取数据的顺序是先尝试读寄存器,如果不存在则尝试读高速缓存,如果还不存在则读RAM,最后才是读ROM。一些CPU有三级cache,读取时是一级一级往下直到找到需要的操作数,一般做的比较好的CPU3级缓存已经能让命中率高达95%以上。
图2-5-2-1 四种存储介质
有了上面的知识再往下探索就水到渠成了,如果把Java内存模型与多级存储机制类比将发现为了提高性能java引入了工作内存的概念,提高了线程执行时读取数据的速度,这样就可以把java模型中的主存和工作内存分别于RAM和高速缓存或寄存器对应起来,每条线程的工作内存预先把需要的数据复制到高速缓存或寄存器(但是不保证所有的工作内存的变量副本都是放在高速缓存,也可能在RAM,具体的还要看JVM是如何实现的),这样在多线程并发时性能得到保证。当然寄存器和高速缓存由于成本原因存在容量大小限制的问题,这个也是考验JVM实现的一个难题。
一般引入一种机制解决了一个问题,但同时也会带来另外一个问题,数据同步即是带来的另一个问题,即是否能保证当前运算使用的变量值总是当前时刻最新的值。如果变量值并非最新值,将会导致数据的脏读,最终可能导致计算结果大相径庭。这时可能有人会想起java中有个volatile关键词,毫无疑问它能保证可见性,让每个线程得到的都是主存中最新的变量值,但它就足以保证数据的同步性了吗?举个典型例子,伪代码如下:
private volatile int count=0;
private void increase(){count++}
public static void main(String args){
创建30条线程执行;
每条线程任务都是执行10000次increase()方法;
}
执行完所有线程任务,我们期望的结果会是30*10000,但实际却是一个小于30*10000的数,刚开始看到一定觉得有点奇怪,但仔细一想就清楚了,count++编译后最终并非一个原子操作,它由几个指令一起组合实现。图2-5-2-2能较清晰地说明此点,在Java内存模型中,count++被分割成5个步骤(当然这个并不是确切的指令执行步骤),这5步不具有原子性,假如在完成过程中,其他线程就去读了主存的count变量,那明显导致了一个脏读现象。
图2-5-2-2
导致这个问题的原因其实是因为volatile不具备锁操作,要解决此问题其实不难,就是将这五步变为原子操作,即保证线程一完成之前不能有其他线程读取count变量,对count变量加一个互斥锁即可达到,线程一在执行第①步前对count加锁,其他线程无法对count进行访问,线程一执行完第⑤步后释放锁,此刻开始才允许其他线程获取此变量。
Volatile是一个很容易搞混的关键词,很多经验丰富的开发人员都不能正确使用它,这节从机器结构讲到对应的java内存模型,再引出主存与工作内存之间数据同步的问题,进而更好地解释了volatile的确切含义——它只保证可见性,它不足以保证数据的同步性。
volatile足以保证数据同步吗的更多相关文章
- volatile并不能保证数据同步、只能保证读取到最新主内存数据
在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配.其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈, 线程栈保存了线程运行时候变量值信息.当线程访问某一个对象时候值的 ...
- 基于 MySQL Binlog 的 Elasticsearch 数据同步实践 原
一.背景 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品.订单等数据的多维度检索. 使用 Elasticsearch 存储业务数据可以 ...
- 基于MySQL Binlog的Elasticsearch数据同步实践
一.为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品.订单等数据的多维度检索. 使用 Elasticsearch 存储业务数 ...
- 线程系列07,使用lock语句块或Interlocked类型方法保证自增变量的数据同步
假设多个线程共享一个静态变量,如果让每个线程都执行相同的方法每次让静态变量自增1,这样的做法线程安全吗?能保证自增变量数据同步吗?本篇体验使用lock语句块和Interlocked类型方法保证自增变量 ...
- volatile之一--volatile不能保证原子性
Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这 ...
- Windows下多线程数据同步互斥的有关知识
对于操作系统而言,在并行程序设计中难免会遇到数据同步和共享的问题,本文针对这个问题,以windows系统为例回顾一下资源同步的相关问题.要点如下: 1.同步和数据共享 数据征用 2.同步原语 ...
- Volatile不保证原子性(二)
Volatile不保证原子性 前言 通过前面对JMM的介绍,我们知道,各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后在写回到主内存中的. 这就可能存在一个线程AAA修改 ...
- 使用 volatile 关键字保证变量可见性和禁止指令重排序
volatile 概述 volatile 是 Java 提供的一种轻量级的同步机制.相比于传统的 synchronize,虽然 volatile 能实现的同步性要差一些,但开销更低,因为它不会引起频繁 ...
- [从源码学设计]蚂蚁金服SOFARegistry 之 LocalDataServerChangeEvent及数据同步
[从源码学设计]蚂蚁金服SOFARegistry 之 LocalDataServerChangeEvent及数据同步 目录 [从源码学设计]蚂蚁金服SOFARegistry 之 LocalDataSe ...
随机推荐
- 【阿里聚安全·安全周刊】互联网时代人类还有被遗忘的权利吗 | Android与中兴
本周七个关键词:互联网时代丨中兴和Android丨安卓厂商和安全补丁丨移动支付安全丨泰国移动运营商泄密丨格式化硬盘的恶意程序丨代码签名滥用 -1- [互联网] 互联网时代 人类还有被遗忘的权利吗 ...
- python day two,while
一.运算符号 算数运算符:+ .-.*././/(取整除).%(去余).** 比较运算符:>.< .>=.<=.== 赋值运算符:=.+=.-=./=.%=.**= 逻辑预算符 ...
- DDD实战进阶第一波(六):开发一般业务的大健康行业直销系统(实现产品上下文仓储与应用服务层)
前一篇文章我们完成了产品上下文的领域层,我们已经有了关于产品方面的简单领域逻辑,我们接着来实现产品上下文关于仓储持久化与应用层的用例如何来协调 领域逻辑与仓储持久化. 首先大家需要明确的是,产品上下文 ...
- Centos常用命令之:文件与目录管理
在centos中常用的文件与目录操作命令有: ◇chmod:修改文件或目录的权限 ◇mkdir:新建目录◇rmdir:删除目录◇rm:删除目录或文件◇cp:复制目录或文件◇mv:移动目录或文件 下面就 ...
- ●UVa 11346 Probability
题链: https://vjudge.net/problem/UVA-11346题解: 连续概率,积分 由于对称性,我们只用考虑第一象限即可. 如果要使得面积大于S,即xy>S, 那么可以选取的 ...
- poj 1845 (逆元 + 约数和)
题意: 求A^B的所有约数(即因子)之和,并对其取模 9901再输出. 思路: A可以表示为A=(p1^k1)*(p2^k2)*(p3^k3)*....*(pn^kn) 其中pi均为素数 那么A的 ...
- WiFi天线分集
0 概述 在调试一款古董级射频芯片时,发现它支持1发2收,由于在画板工程师将辅助天线也整出来.等板子贴出来后,就与同事一起折腾这个分集接收功能. 碰到过如下问题,先记录,以便后期有空再继续. 1)发现 ...
- java开发笔记
replace与replaceAll的区别 replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换(CharSequence即字符串序列的意思,说白了也就是 ...
- 简述RIP路由协议和OSPF路由协议的相同点和不同点。
路由协议分为静态路由协议和动态路由协议.动态路由协议有很多种,如RIP.OSPF.EIGRP等. 1.RIP(路由信息协议)是路由器生产商之间使用的第一个开放标准.RIP有两个版本:RIPv1和RIP ...
- python安装pip以及导入第三方包
python有着强大的第三方库,数量很多且功能强大. 最原始的办法是在官网上下载压缩包,解压,然后运行setup.py来进行安装. 显然这种方法很繁琐,不方便.因此有了包管理工具. pip是一个包管理 ...