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 ...
随机推荐
- [IOI 2011]ricehub
Description 乡间有一条笔直而长的路称为“米道”.沿着这条米道上 R 块稻田,每块稻田的坐标均为一个 1 到 L 之间(含 1 和 L)的整数.这些稻田按照坐标以不减的顺序给出,即对于 0 ...
- [HNOI2009]图的同构
Description 求两两互不同构的含n个点的简单图有多少种. 简单图是关联一对顶点的无向边不多于一条的不含自环的图. a图与b图被认为是同构的是指a图的顶点经过一定的重新标号以后,a图的顶点集和 ...
- 51 nod 1681 公共祖先 (主席树+dfs序)
1681 公共祖先 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 有一个庞大的家族,共n人.已知这n个人的祖辈关系正好形成树形结构(即父亲向儿子连边). 在另 ...
- hdu 5739 割点
Fantasia Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total S ...
- hdu 4897 树链剖分(重轻链)
Little Devil I Time Limit: 16000/8000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others ...
- hdu 5464(dp)
题意: 给你n个数,要求选一些数(可以不选),把它们加起来,使得和恰好是p的倍数(0也是p的倍数),求方案数. - - 心好痛,又没想到动规 #include <stdio.h> #inc ...
- SPOJ Query on a tree V
You are given a tree (an acyclic undirected connected graph) with N nodes. The tree nodes are number ...
- MQ(消息队列)常见的应用场景解析
前言 提高系统性能首先考虑的是数据库的优化,之前一篇文章<数据库的使用你可能忽略了这些>中有提到过开发中,针对数据库需要注意的事项.但是数据库因为历史原因,横向扩展是一件非常复杂的工程,所 ...
- unittest测试框架详谈及实操(二)
类级别的setUp()方法与tearDown()方法 在实操(一)的例子中,通过setUp()方法为每个测试方法都创建了一个Chrome实例,并且在每个测试方法执行结束后要关闭实例.是不是觉得有个多余 ...
- Dynamics 365中使用Web API将查找字段的值设置为空值的方法。
摘要: 本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复270或者20180424可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyon ...