再有人问你volatile是什么,把这篇文章也发给他(深入分析)

转载自  再有人问你volatile是什么,把这篇文章也发给他

在上一篇文章中,我们围绕volatile关键字做了很多阐述,主要介绍了volatile的用法、原理以及特性。在上一篇文章中,我提到过:volatile只能保证可见性和有序性,无法保证原子性。关于这部分内容,有读者阅读之后表示还是不是很理解,所以我再单独写一篇文章深入分析一下。阅读本文之前,请先阅读上一篇文章:再有人问你volatile是什么,就把这篇文章发给他

 

volatile与有序性

在上一篇文章中我们提到过:volatile一个强大的功能,那就是他可以禁止指令重排优化。通过禁止指令重排优化,就可以保证代码程序会严格按照代码的先后顺序执行。那么volatile又是如何禁止指令重排的呢?

先给出结论:volatile是通过内存屏障来来禁止指令重排的。

内存屏障(Memory Barrier)是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。下表描述了和volatile有关的指令重排禁止行为:

从上表我们可以看出:

当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。

当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。

当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。

具体实现方式是在编译期生成字节码时,会在指令序列中增加内存屏障来保证,下面是基于保守策略的JMM内存屏障插入策略:

  • 在每个volatile写操作的前面插入一个StoreStore屏障。

    • 对于这样的语句Store1; StoreLoad; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

  • 在每个volatile写操作的后面插入一个StoreLoad屏障。

    • 对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

  • 在每个volatile读操作的后面插入一个LoadLoad屏障。

    • 对于这样的语句Load1;LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

  • 在每个volatile读操作的后面插入一个LoadStore屏障。

    • 对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

所以,volatile通过在volatile变量的操作前后插入内存屏障的方式,来禁止指令重排,进而保证多线程情况下对共享变量的有序性。

volatile与可见性

在上一篇文章中我们提到过:Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。

其实,volatile对于可见性的实现,内存屏障也起着至关重要的作用。因为内存屏障相当于一个数据同步点,他要保证在这个同步点之后的读写操作必须在这个点之前的读写操作都执行完之后才可以执行。并且在遇到内存屏障的时候,缓存数据会和主存进行同步,或者把缓存数据写入主存、或者从主存把数据读取到缓存。

这里稍微拓展一下,我们在内存模型是怎么解决缓存一致性问题的一文中介绍过缓存一致性协议,同时也提到过内存一致性模型的实现可以通过缓存一致性协议来实现。同时,留了一个问题:已经有了缓存一致性协议,为什么还需要volatile?

这个问题的答案可以从多个方面来回答:

1、并不是所有的硬件架构都提供了相同的一致性保证,Java作为一门跨平台语言,JVM需要提供一个统一的语义。

2、操作系统中的缓存和JVM中线程的本地内存并不是一回事,通常我们可以认为:MESI可以解决缓存层面的可见性问题。使用volatile关键字,可以解决JVM层面的可见性问题。

3、缓存可见性问题的延伸:由于传统的MESI协议的执行成本比较大。所以CPU通过Store Buffer和Invalidate Queue组件来解决,但是由于这两个组件的引入,也导致缓存和主存之间的通信并不是实时的。也就是说,缓存一致性模型只能保证缓存变更可以保证其他缓存也跟着改变,但是不能保证立刻、马上执行。

其实,在计算机内存模型中,也是使用内存屏障来解决缓存的可见性问题的(再次强调:缓存可见性和并发编程中的可见性可以互相类比,但是他们并不是一回事儿)。

写内存屏障(Store Memory Barrier)可以促使处理器将当前store buffer(存储缓存)的值写回主存。读内存屏障(Load Memory Barrier)可以促使处理器处理invalidate queue(失效队列)。进而避免由于Store Buffer和Invalidate Queue的非实时性带来的问题。

所以,内存屏障也是保证可见性的重要手段,操作系统通过内存屏障保证缓存间的可见性,JVM通过给volatile变量加入内存屏障保证线程之间的可见性。

内存屏障

再来总结一下Java中的内存屏障:用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。

 

volatile与原子性

在以前的文章中,我们介绍synchronized的时候,提到过,为了保证原子性,需要通过字节码指令monitorentermonitorexit,但是volatile和这两个指令之间是没有任何关系的。volatile是不能保证原子性的。

网上有很多文章,拿i++的例子说明volatile不能保证原子性,然后进行各种分析,有的说由于引入内存屏障导致无法保证原子性,有的说一段i++代码,在编译后字节码为:

10: getfield      #2                  // Field i:I
14: iconst_1
15: iadd
16: putfield      #2                  // Field i:I

在不考虑内存屏障的情况下,一个i++指令也包含了四个步骤。

这些分析,只是说明了i++本身并不是一个原子操作,即使使用volatile修饰i,也无法保证他是一个原子操作。并不能解释为什么volatile为啥不能保证原子性。

要我说,由于CPU按照时间片来进行线程调度的,只要是包含多个步骤的操作的执行,天然就是无法保证原子性的。因为这种线程执行,又不像数据库一样可以回滚。如果一个线程要执行的步骤有5步,执行完3步就失去了CPU了,失去后就可能再也不会被调度,这怎么可能保证原子性呢。

为什么synchronized可以保证原子性 ,因为被synchronized修饰的代码片段,在进入之前加了锁,只要他没执行完,其他线程是无法获得锁执行这段代码片段的,就可以保证他内部的代码可以全部被执行。进而保证原子性。

但是synchronized对原子性保证也不绝对,如果真要较真的话,一旦代码运行异常,也没办法回滚。所以呢,在并发编程中,原子性的定义不应该和事务中的原子性一样。他应该定义为:一段代码,或者一个变量的操作,在没有执行完之前,不能被其他线程执行。

那么,为什么volatile不能保证原子性呢?因为他不是锁,他没做任何可以保证原子性的处理。当然就不能保证原子性了。

 

总结

本文在上一篇文章的基础上,再次介绍了volatile和原子性、有序性以及可见性之间的关系。有序性和可见性是通过内存屏障实现的。而volatile是无法保证原子性的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325230.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cockroach DB 1.0发布

分布式SQL数据库Cockroach DB遵循软件产品以动物命名的模式。近日,该数据库的第一个生产就绪版本1.0发布。 许多人将Cockroach DB视为Google Spanner的开源版本。后者是一个强一致性、横向可扩展的RDBMS,它起初是一个服务于谷歌服务的内部项目&#xff…

分享10道常考Java面试题及答案

转载自 分享10道常考Java面试题及答案 Hi&#xff0c;大家好&#xff0c;今天给大家分享10道常考的Java面试题及答案&#xff0c;涉及到Java中的10个方面。每个领域一道题。希望你能有收获。 <面向对象>什么是面向对象&#xff1f;什么是面向过程&#xff1f;面型对象…

对数据进行插入操作并且获取主键的值

eg. eg:工作中写定时模块&#xff0c;先插入时间段&#xff0c;然后直接获取id

编写高性能 .NET 代码 第二章:垃圾回收 基本操作

基本操作 垃圾回收的算法细节还在不断完善中&#xff0c;性能还会有进一步的提升。下文介绍的内容在不同的.NET版本里会略有不同&#xff0c;但大方向是不会有变动的。 在.net进程里会管理2个类型的内存堆&#xff1a;托管和非托管。本地代码申请的&#xff0c;以及由CLR申请…

如何设计一个高可用的运营系统

转载自 如何设计一个高可用的运营系统 这是一篇来自粉丝的投稿&#xff0c;作者【林湾村龙猫】近一年在做关于运营活动方面的设计。本文是他的关于运营活动的总结&#xff0c;Hollis做了一点点修改。 概述 一个产品业务的发展总是离不开运营二字。随着业务快速的发展以及新…

.Net中的AOP系列之《AOP实现类型》

本系列的实验环境&#xff1a;VS 2017。 读完本章后&#xff0c;可能仍然不能实现自己的AOP工具&#xff0c;但应该对两种主要类型&#xff08;PostSharp和Castle DynamicProxy&#xff09;的AOP工具的运行原理有了基本的理解。PostSharp是一个在编译时编织的后期编译器&#x…

JavaFX官方教程(一)之JavaFX概述

翻译自 JavaFX概述 本章概述了可以使用JavaFX API构建的应用程序类型&#xff0c;下载JavaFX库的位置以及有关正在交付的关键JavaFX功能的高级信息。 JavaFX是一组图形和媒体包&#xff0c;使开发人员能够设计&#xff0c;创建&#xff0c;测试&#xff0c;调试和部署在不同平…

mybatisPlus的分页查询

结论&#xff1a;不是直接limit进行分页的 而是通过rowBounds进行的

详解CockroachDB事务处理系统

本文提到的一些术语&#xff0c;比如Serializability和Linearizability&#xff0c;解释看Linearizability, Serializability and Strict Serializability。 本文中观点大部分都是参考了CockroachDB多篇官方blog,设计文档&#xff0c;代码以及相关资料&#xff0c;相对来说比较…

JavaFX官方教程(二)之JavaFX体系结构

翻译自 JavaFX体系结构 本章提供了JavaFX体系结构和生态系统的高级描述。 图2-1说明了JavaFX平台的架构组件。图中的部分描述了每个组件以及这些部件如何互连。JavaFX公共API下面是运行JavaFX代码的引擎。它由包含JavaFX高性能图形引擎的子组件组成&#xff0c;称为Prism; …

Work Time Manager【开源项目】- 创建自己日志组件 2.0重构

这次我们真是开始来聊聊开源项目里&#xff0c;小而有用的模块或者组件的开发思想。 同时&#xff0c;软件已经更新到1.60的版本了&#xff0c;支持新用户注册&#xff0c;可以不再使用统一的test账户了。 您可以通过以下路径进行下载&#xff1a; 1、在GitHub上fellow一下项目…

JavaFX官方教程(三)之JavaFX示例应用程序入门

翻译自 JavaFX示例应用程序入门 此示例应用程序集旨在帮助您开始使用常见的JavaFX任务&#xff0c;包括使用布局&#xff0c;控件&#xff0c;样式表&#xff0c;FXML和视觉效果。 Hello World&#xff0c;JavaFX Style JavaFX中的表单设计 用CSS设计的花式设计 使用F…

SSH(Spring+Struts2+Hibernate)框架搭建步骤(含配置文件以及运行结果)

1.创建web项目2.导入ssh 所需要的多有jar包&#xff0c;到web-inf下面的lib里面3.将导入过来的jar包都build--path一下4.切换到myeclipse database视图中&#xff0c;添加链接数据库的链接5.新建一个数据库连接&#xff08;如果忘记了数据库链接时你可以去下面的网址中查看&…

Unity 游戏用XLua的HotFix实现热更原理揭秘

本文通过对XLua的HoxFix使用原理的研究揭示出来这样的一套方法。这个方法的第一步&#xff1a;通过对C#的类与函数设置Hotfix标签。来标识需要支持热更的类和函数。第二步&#xff1a;生成函数连接器来连接LUA脚本与C#函数。第三步&#xff1a;在C#脚本编译结束后&#xff0c;使…

JavaFX官方教程(四)之Hello World,JavaFX样式

翻译自 Hello World&#xff0c;JavaFX Style 教你创建和构建JavaFX应用程序的最佳方法是使用“Hello World”应用程序。本教程的另一个好处是&#xff0c;它使您能够测试您的JavaFX技术是否已正确安装。 本教程中使用的工具是NetBeans IDE 7.4。在开始之前&#xff0c;请确…

WebAssembly,开发者赢了

自从WebAssembly标准发布以及各大浏览器完成对其默认支持之后&#xff0c;WebAssembly成为前端热门话题。在WebAssembly之前&#xff0c;类似的前端二进制标准有火狐主导的asm.js和Chrome主导的PNaCl。二者均用于将后端C/C代码用于前端&#xff0c;作为它们折中方案&#xff0c…

JavaFX官方教程(五)之在JavaFX中创建表单

翻译自 在JavaFX中创建表单 在开发应用程序时&#xff0c;创建表单是一项常见活动。本教程将向您介绍屏幕布局的基础知识&#xff0c;如何将控件添加到布局窗格以及如何创建输入事件。 在本教程中&#xff0c;您将使用JavaFX构建如图4-1所示的登录表单。 图4-1登录表单 本入…