并发过程中volatile能否保证线程安全
关于存储介质先搞清四种存储介质:寄存器、高级缓存、RAM和ROM 。 RAM与ROM大家都比较熟悉了 , 可以看成是我们经常说的内存与硬盘 。 寄存器属于处理器里面的一部分 , 而高级缓存cache是CPU设计者为提高性能引入的一个缓存 , 也可以说是属于处理器的一部分 。
文章插图
四种存储介质
为什么需要它们【并发过程中volatile能否保证线程安全】在利用CPU进行运算时必定涉及操作数的读取 , 假如CPU直接读取ROM , 那么这个读取速度简直是无法忍受的 , 于是引入了内存RAM 。 这样做确实让速度提高了很多 , 但由于CPU发展十分迅猛 , 另一方面RAM的发展受到技术及成本的限制而发展缓慢 , 此时就产生了一个很难调和的矛盾:CPU运算速度比从RAM读取数据的速度快了几个数量级 。 木桶原理我们都很熟悉的了 , 桶的容量大小取决于最短的那块 。 由于存在这个矛盾 , 它必将会影响处理器的效率 , 于是又引入了高级缓存 。 直接在CPU添加了几个不同级别的缓存 , 虽然它们的速度无法与寄存器相比 , 但是速度已经提升很多 , 基本能跟CPU的计算速度相匹配 。
总结成一句话就是:为了解决CPU运算速度与读取速度的矛盾 , 引入了多种存储机制 。 读取速度快慢的排序如下:寄存器>cache>RAM>ROM 。 用一个比较好理解但不完全正确的概念来解释 。 因为寄存器是离CPU最近的 , 所以读取最快 。 高速缓存次之 , RAM第三 , ROM离得最远 , 自然速度最慢 。 当然不能完全用距离来说明这个问题 , 但用距离是比较好理解的 。 另外的影响因素还包括硬件设计不同、工作方式不同 。
文章插图
读取速度
介质如何工作机器的四种存储介质是有关系的 , 一般程序运行时会将ROM相关的程序数据都读进RAM中 , 而需要运算的数据或运算过程中即将要用到的数据则会被读进高速缓存或寄存器中 。 假如要进行的运算所需要的所有数据及指令都在寄存器和高速缓存中 , 则这个运算过程则表现得非常平坦 。 此时不存在性能瓶颈 , 因为运算速度跟读取速度基本匹配 。
CPU读取数据的顺序是先尝试读寄存器 , 如果不存在则尝试读高速缓存 。 如果还不存在则读RAM , 最后才是读ROM 。 一般CPU有三级cache , 读取时是一级一级往下直到找到需要的操作数 , 做的比较好的CPU的3级缓存能让命中率达到95%以上 。
文章插图
Java内存模型有了上面的知识再往下探索就水到渠成了 , 如果把Java内存模型与多级存储机制类比我们能够发现Java为了提高性能而引入了工作内存的概念 。 可以把Java模型中的主存和工作内存分别与RAM和高速缓存或寄存器对应起来 , 每条线程的工作内存预先把需要的数据复制到高速缓存或寄存器(但是不保证所有的工作内存的变量副本都是放在高速缓存 , 也可能在RAM , 具体的还要看JVM是如何实现的) , 这样就提高了线程执行时读取数据的速度 , 在多线程并发时性能得到保证 。 当然寄存器和高速缓存由于成本原因存在容量大小限制的问题 , 这个也是考验JVM实现的一个难题 。
文章插图
Java内存模型
数据同步问题一般当我们引入一种机制解决了某个问题 , 同时也会带来另外一些问题 。 数据同步即是带来的另一个问题 , 即是否能保证当前运算使用的变量值总是当前时刻最新的值 。 如果变量值并非最新值 , 将会导致数据的脏读 , 最终可能导致计算结果大相径庭 。 这时可能有人会想起Java中有个volatile关键词 , 毫无疑问它能保证可见性 , 让每个线程得到的都是主存中最新的变量值 , 但它是否足以保证数据的同步性呢?
推荐阅读
- Redis集群做法的难点,百万并发客户端「实战」
- 为什么 Redis 单线程能支撑高并发?
- 针对智能城市建设过程中的三大痛点,京东数科如何解决?
- “天线宝宝”这样“举高高”
- 解决 idea 使用过程中让你觉得不爽的一些问题
- Java并发编程-线程基础
- 掌握这些高并发面试题,保证你能造火箭拿offer
- 具有Java基础,在自学Python的过程中需要注意哪些内容
- 从零手写并发框架(四)异步转同步 springboot 整合
- 并发控制工具相位器Phaser实现原理