原子性、内存可见性和重排序——重新认识synchronized和volatile
一、原子性
原子性操作指相应的操作是单一不可分割的操作。例如,对int变量count执行count++d操作就不是原子性操作。因为count++实际上可以分解为3个操作:(1)读取变量count的当前值;(2)拿count的当前值和1做加法运算;(3)将加完后的值赋给count变量。
在多线程环境中,非原子操作可能会受其他线程的干扰。比如,上述例子如果没有对相应的代码进行同步(Synchronization)处理,则可能出现在执行第2个操作的时候,count变量的值已经被其他线程修改过了。当然,synchronized关键字可以帮助我们实现原子性操作,以避免这种线程间的干扰情况。
synchronized关键字可以实现操作的原子性,其实质是:通过该关键字所包括的临界区(Critical Section)的排他性保证在任何一个时刻只有一个线程能够执行临界区中的代码,这使得临界区中的代码代表了一个原子操作。这一点,大家基本都很清楚。但是,synchronized关键字所起到的另一个作用——保证内存的可见性(Memory Visibility),也是我们值得回顾的地方。
二、内存可见性
CPU在执行代码的时候,为了减少变量访问的时间消耗可能将代码中访问的变量的值缓存到该CPU缓存区中,因此,相应的代码再次访问该变量的时候,相应的值可能从CPU缓存中而不是主内存中读取的。同样的,代码对这些被缓存过的变量的值的修改也可能仅是被写入CPU缓存区,而没有写入主内存。由于每个CPU都有自己的缓存区,因此一个CPU缓存区中的内容对于其他CPU而言是不可见的。这就导致了在其他CPU上运行的其他线程可能无法“看到”该线程对某个变量值的更改。这就是所谓的内存可见性。
synchronized关键字的另一个作用就是保证了一个线程执行临界区中的代码时,所修改的变量值对于稍后执行该临界区的线程来说是可见的。这对于保证多线程代码的正确性来说非常重要。
而volatile关键字也能够保证内存可见性。即一个线程对一个采用volatile关键字修饰的变量的值的更改,对于其他访问该变量的线程而言总是可见的。也就是说,其他线程不会读到一个“过期”的变量值。因此,有人将volatile关键字和synchronized关键字所代表的内部锁做比较,将其称为轻量级的锁。这种称呼其实并不恰当,volatile关键字只能保证内存可见性,它并不能像synchronized关键字所代表的内部锁那样能够保证操作的原子性。volatile关键字实现内存可见性的核心机制是:当一个线程修改了一个volatile修饰的变量的值时,该值会被写入主内存(即RAM)而不仅仅是当前线程所在的CPU的缓存区,而其他CPU的缓存区中存储的该变量的值也会因此而失效(从而得以更新为主内存中该变量的“新值”)。这就保证了其他线程访问该volatile修饰的变量时,总是可以获取到该变量的最新值。
三、指令重排序
volatile关键字的另一个作用是:它禁止了指令重排序(Re-order)。编译器和CPU为了提高指令的执行效率可能会进行指令重排序,这使得代码的实际执行方式可能不是按照我们所认为的方式进行。例如下面的实例变量初始化语句:
private SomeClass someObject = new SomeClass();
上述语句非常简单:(1)创建类SomeClass 的实例;(2)将类SomeClass 的实例的引用赋给变量someObject 。但是由于指令的重排序作用,这段代码的实际执行顺序可能是:(1)分配一段用于存储SomeClass 实例的内存空间;(2)将对该内存空间的引用赋给变量someObject;(3)创建类SomeClass 的实例。因此,当其他线程访问someObject变量的值时,其得到的仅是指向一段存储SomeClass 实例的的内存空间的引用而已,而该内存空间相应的SomeClass 实例的初始化可能尚未完成,这就可能导致一些意想不到的结果。而禁止指令重排序则是可以使得上述代码按照我们所期望的顺序(正如代码所表达的顺序)来执行。
禁止指令重排序虽然导致编译器和CPU无法对一些指令进行可能的优化,但是它某种程度上让代码执行看起来更符合我们的期望。
四、Volatile、synchronized两者的区别联系
1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
3.volatile仅能实现变量的修改可见性,不能保证原子性(线程A修改了变量还没结束时,另外的线程B可以看到已修改的值,而且可以修改这个变量,而不用等待A释放锁,因为Volatile 变量没上锁);而synchronized则可以保证变量的修改可见性和原子性。
4.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞和上下文切换。
5.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
文章摘写自:https://blog.csdn.net/ljheee/article/details/53152737
原子性、内存可见性和重排序——重新认识synchronized和volatile的更多相关文章
- Java内存模型(三)原子性、内存可见性、重排序、顺序一致性、volatile、锁、final
一.原子性 原子性操作指相应的操作是单一不可分割的操作.例如,对int变量count执行count++d操作就不是原子性操作.因为count++实际上可以分解为3个操作:(1)读取变量co ...
- volotile关键字的内存可见性及重排序
在理解volotile关键字的作用之前,先粗略解释下内存可见性与指令重排序. 1. 内存可见性 Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,并且线程 ...
- Java内存模型_重排序
重排序:是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段 1..编译器优化的重排序.编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序. 2..指令级并行的重排序.现 ...
- 《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before
第16章 Java内存模型 终于看到这本书的最后一章了,嘿嘿,以后把这本书的英文版再翻翻.这本书中尽可能回避了java内存模型(JMM)的底层细节,而将重点放在一些高层设计问题,例如安全发布,同步策略 ...
- 【死磕Java并发】-----Java内存模型之重排序
在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件: 在单线程环境下不能改变程序运行的结果: 存在数据依赖关系的不 ...
- Java内存模型之重排序
参考链接:https://blog.csdn.net/huzhigenlaohu/article/details/51595676
- java内存模型——重排序
线程安全问题概括来说表现为三个方面:原子性,可见性和有序性. 在多核处理器的环境下:编译器可能改变两个操作的先后顺序:处理器可能不是完全依照程序的目标代码所指定的顺序执行命令:一个处理器执行的多个操作 ...
- 【java多线程系列】java内存模型与指令重排序
在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...
- JMM中的重排序及内存屏障
目录 1. 概述 2. 重排序 2-1. as-if-serial语义 2-2. 重排序的种类 2-3. 从Java源代码到最终实际执行的指令序列, 会分别经历下面3中重排序. 3. 内存屏障类型 3 ...
随机推荐
- 2 oracle 实现上下键翻历史命令 rlwrap
1.下载 rlwrap 环境:VMware虚拟机 redhat 7.0 oracle 12c 下载rlwrap:http://files.cnblogs.com/files/kil ...
- Drupal7 配置多站点及为每个站点设置语言
默认情况 在Drupal7的安装目录下存在sites目录 sites目录结构如下: --all --default --example.sites.php --README.txt 1. 添加新域名, ...
- 收集的PHP工具及类库
composer PHP的依赖管理工具 phpmig PHP的数据库迁移工具,依赖于composer Requests for PHP HTTP请求库,采集页面可以用到的 ...
- 向大学说拜拜——大学 > 兴趣 + 时间 + 思考 + 实践
[人物素描] 大学期间,担任过班委,加入过学生会,参加过社团,拿过奖学金......而印象最深刻的莫过于参加并组织过ACM集训,以及参加过导师的国家自然科学基金项目了.毕业时顺利拿到一波offer,并 ...
- php编程知识点2018
一 .PHP基础部分 1.PHP语言的一大优势是跨平台,什么是跨平台? PHP的运行环境最优搭配为Apache+MySQL+PHP,此运行环境可以在不同操作系统(例如windows.Linux等)上配 ...
- 「日常训练」 Fire!(UVA-11624)
与其说是训练不如说是重温.重新写了Java版本的代码. import java.util.*; import java.math.*; import java.io.BufferedInputStre ...
- 180724-统计JVM进程中线程数两种方式小记
I. 统计进程中的线程数 相关系列博文推荐: 180711-JVM定位分析CPU性能消耗 180704-JDK常用监控参数 jvm调优的工具介绍 1. proc查询 /proc 目录以可读文本文件形式 ...
- Lua学习笔记(7): 模块
模块 模块就像是c语言工程项目目录里的.h.c文件或外部依赖项,为某一个文件的代码提供依赖,其实就是把工作分成几个模块,方便项目的管理,提高开发效率和维护效率 在Lua中,模块其实就是一个表,实现方式 ...
- Oracle集合
--union 并集 select * from emp where ename like '%A%' union select * from emp where ename like '%M%'; ...
- mybatis 加载配置文件的方法
一. 使用sqlSessionFactory 的 mapperLocations 进行加载 <!-- SessionFactory --> <bean id="sqlSe ...