原子性、内存可见性和重排序——重新认识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 ...
随机推荐
- CF 1093 E. Intersection of Permutations
E. Intersection of Permutations 链接 题意: 给定两个序列,询问第一个排列的[l1,r1]和第二个排列[l2,r2]中有多少个共同的数,支持在第二个排列中交换两个数. ...
- hdu2061 Treasure the new start, freshmen!(暴力简单题)
Treasure the new start, freshmen! Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/3276 ...
- katalon系列十:Katalon Studio自定义关键字之拖拽
Katalon Studio自带关键字“Drag And Drop To Object”,可以在这个网站实践:http://jqueryui.com/droppable/#default 不过“Dra ...
- 学习笔记之glog的使用
下载源码,使用cmake编译,最后得到了32位的静态库 glog.lib 使用库时要注意添加以下预定义: GLOG_NO_ABBREVIATED_SEVERITIES; GOOGLE_GLOG_DLL ...
- 个人安装ss的一个记录
在ubuntu16.04安装ss服务.由于lantern最近极其不稳定(我还花钱的qaq),经常断联以至于几乎废了,莫得办法,只好花钱搭一个了orz...呵,贫穷.... 安装shadowsocks ...
- 微信小程序转换为百度小程序
据粗略预估,微信小程序和百度小程序,有至少90%以上的相似代码,而且api的参数和返回的数据都是一致的,有一些不一致的将做如下介绍:.wxml文件,改成后辍名.swan.wxss文件,改成后辍名为.c ...
- linux源码学习-for_each_cpu
刚开始读linux源码,第一眼就看到了这个很有意思的函数族,周末好好研究一下 3.13 这个组都是宏定义for循环,分析的时候注意到cpumask_next(),它在一个文件中定义了两次,还不是重载, ...
- nginx响应client的处理机制
nginx与apache的不同响应机制——epoll nginx可以处理上百万级别的并发请求就是源至于异步非阻塞的处理机制,异步非阻塞核心即是epoll nginx内部反向代理
- mysql 对表格加索引但原表格有重复数据
1.把表中唯一数据搜索创建临时表,最后代替原先表. create table mmmmmm as SELECT * FROM meriadianannotation GROUP BY SeriesID ...
- c# 消息机制篡改
1.背景介绍: c#程序想要针对某个的消息进行别的行为.例如:窗体不可拖动. 2.应用函数WinProc 以窗口不可拖动举例: const int WM_NCLBUTTONDOWN = 0x00A1; ...