Jvm 中的 重排序、主存、原子操作
一、重排序
好处:重排序可以提升性能,避免在一个耗时很长的指令在“执行”阶段呆很长时间,而导致后续的指令都卡在“执行”之前的阶段上。
坏处:重排序对多线程的影响
- class ReorderExample {
- int a = 0;
- boolean flag = false;
- public void writer() {
- a = 1; //1
- flag = true; //2
- }
- Public void reader() {
- if (flag) { //3
- int i = a * a; //4
- ……
- }
- }
- }
1) 数据依赖性:所以有数据依赖性的语句不能进行重排序。
2) as-if-serial:如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。但是不管怎么重排序,必须保证单线程程序的执行结果不能被改变,故单线程中重排序不需要被禁止(不影响执行结果)。
- double pi = 3.14; // Ⓐ
- double r = 1.0; // Ⓑ
- double area = pi * r * r; // Ⓒ
Ⓐ -> Ⓑ -> Ⓒ 按程序顺序的执行结果:area = 3.14
Ⓑ -> Ⓐ -> Ⓒ 按重排序后的执行结果:area = 3.14
注:as-if-serial 语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,写单线程的程序员有一个幻觉:单线程程序是按程序写的顺序来执行的。
3) happens-before 规则
- 程序顺序规则:按照程序代码的执行流顺序,(时间上)先执行的操作happen—before(时间上)后执行的操作。
- 监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
- volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
- 传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。
二、主存与可见性
1. 主存与工作内存
计算机系统中,为了尽可能地避免处理器访问主内存的时间开销,处理器大多会利用缓存(cache)以提高性能。其模型如下图所示:
在这种模型下会存在一个现象,即缓存中的数据与主内存的数据并不是实时同步的,各CPU(或CPU核心)间缓存的数据也不是实时同步的。这导致在同一个时间点,各CPU所看到同一内存地址的数据的值可能是不一致的。从程序的视角来看,就是在同一个时间点,各个线程所看到的共享变量的值可能是不一致的。
有的观点会将这种现象也视为重排序的一种,命名为“内存系统重排序”。因为这种内存可见性问题造成的结果就好像是内存访问指令发生了重排序一样(实际上内存指令没有重排序,仅仅是内存可见性问题导致的结果)。
Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己独立的工作内存,里面保存该线程的使用到的变量副本(该副本就是主内存中该变量的一份拷贝 )。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
2. 可见性
内存可见性其针对的是 共享资源在工作内存与主存之间的相互访问。
保证可见性的原义:指的是工作内存中对共享资源修改立即刷入到主存中,并且主存中的值立即同步到其它工作内存中。
它的实际实现方式可以是:
1) 使用 synchronized,线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。
2) 使用 volatile,也就是对volatile变量执行写操作时,会在写操作后加入一条store指令,即强迫线程将最新的值刷新到主内存中,而其他工作内存中的值失效;而在读操作时,会加入一条load指令,即强迫从主内存(工作内存中的值已经失效)中读入变量的值。volatile 防止重排序针对的是内存屏障。但volatile不保证volatile变量的原子性。
三、原子操作
原子操作是指整个操作过程不会被线程调度机制打断。
使用 voilate 关键字 既可避免重排序问题,又可避免内存可见性问题,但无法解决非原子性问题。比如自增操作就不是原子性操作:
- public class Test {
- public volatile int inc = 0;
- public void increase() {
- inc++;
- }
- public static void main(String[] args) {
- final Test test = new Test();
- for(int i = 0; i < 10; i++) {
- new Thread() {
- public void run() {
- for(int j = 0; j< 1000; j++)
- test.increase();
- };
- }.start();
- }
- while(Thread.activeCount() > 1)
- //保证前面的线程都执行完
- Thread.yield();
- System.out.println(test.inc);
- }
- }
这段程序的输出结果是多少?也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字。
原因在于,自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:
a) 线程1对变量进行自增操作:线程1先读取变量inc的原始值,然后线程1被阻塞了(还没有 inc 的值);
b) 然后线程2对变量进行自增操作:线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓 存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。
c) 然后线程1接着进行加1操作,由于已经读取了inc的值,此时线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。
d) 那么两个线程分别进行了一次自增操作后,inc只增加了1。
Jvm 中的 重排序、主存、原子操作的更多相关文章
- JS中数组重排序方法
在数组中有两个可以用来直接排序的方法,分别是reverse()和sort().下面通过本文给大家详细介绍,对js数组重排序相关知识感兴趣的朋友一起看看吧 1.数组中已存在两个可直接用来重排序的方法:r ...
- JMM中的重排序及内存屏障
目录 1. 概述 2. 重排序 2-1. as-if-serial语义 2-2. 重排序的种类 2-3. 从Java源代码到最终实际执行的指令序列, 会分别经历下面3中重排序. 3. 内存屏障类型 3 ...
- Javascript中数组重排序方法详解
在数组中有两个可以用来直接排序的方法,分别是reverse()和sort().下面通过本文给大家详细介绍,对js 数组重排序相关知识感兴趣的朋友一起看看吧. 1.数组中已存在两个可直接用来重排序的方法 ...
- JVM的重排序
重排序一般是编译器或执行时环境为了优化程序性能而採取的对指令进行又一次排序执行的一种手段.重排序分为两类:编译期重排序和执行期重排序,分别相应编译时和执行时环境. 在并发程序中,程序猿会特别关注不同进 ...
- JVM学习(八)指令重排序
一.数据依赖性 在学习JVM的指令重排序之前,我们先了解一下什么是数据依赖性: 编译器和处理器在处理具体的指令时,可能会对操作进行重排序来提高执行性能[多条指令并行执行,所以提升性能的同时也可能会导致 ...
- volotile关键字的内存可见性及重排序
在理解volotile关键字的作用之前,先粗略解释下内存可见性与指令重排序. 1. 内存可见性 Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,并且线程 ...
- java基础之 重排序
重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段.重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境. 在并发程序中,程序员会特别关注不同进程 ...
- Java内存访问重排序笔记
>>关于重排序 重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段. 重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境. > ...
- JVM并发机制的探讨——内存模型、内存可见性和指令重排序
并发本来就是个有意思的问题,尤其是现在又流行这么一句话:“高帅富加机器,穷矮搓搞优化”. 从这句话可以看到,无论是高帅富还是穷矮搓都需要深入理解并发编程,高帅富加多了机器,需要协调多台机器或者多个CP ...
随机推荐
- 使用ArcMap做一个1:5000标准分幅图并编号
实现这个project,十进制度.渔网工具.Excel if/Text函数.挂接Excel表.空间连接.投影这些知识是必须的.看懂本篇博文也就意味着大概掌握了以上知识. 坐标数据设置与编号标准依据&l ...
- Latex "Error: File ended while scanning use of \@xdblarge"
Latex 编译时出现 Error: File ended while scanning use of \@xdblarge" 是因为少了一个 }...
- [物理学与PDEs]第4章习题2 反应力学方程组形式的化约 - 能量守恒方程
试证明: 利用连续性方程及动量方程, 能量守恒方程 (2. 15) 可化为 (2. 21) 的形式. 证明: 注意到 $$\beex \bea &\quad\cfrac{\p}{\p t}\s ...
- python中的__len__,__getitem__ __setitem__ __delitem__ __contains__
可变集合需要实现: __len__ __getitem__ __setitem__ __delitem__不可变集合需要实现: __len__ __getitem__ __len__:返回 ...
- Python3:几行代码实现阶乘
阶乘:一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1.自然数n的阶乘写作n!. #---------------------------------- 阶乘- ...
- laravel5.4 打印输出 sql 语句
直接打印 sql 语句 DB::connection('test_link')->enableQueryLog(); //执行代码 $log = DB::connection('test_lin ...
- Visual Studio Code 的使用方法和技巧
VSCode是微软推出的一款轻量编辑器,采取了和VS相同的UI界面,搭配合适的插件可以优化前端开发的体验. 布局:左侧是用于展示所要编辑的所有文件和文件夹的文件管理器,依次是`资源管理器`,`搜索`, ...
- 【easy】202. Happy Number
happy number Write an algorithm to determine if a number is "happy". A happy number is a n ...
- GPS车辆监控系统的启动方式
我们通常用到的GPS车辆监控系统都有哪些启动方式,又有什么区别呢?通常GPS车辆监控系统都有热启.冷启.温启的技术指标,现参考如下:GPS开机定位分为冷启动.温启动和热启动三种:一.冷启动:以下几种情 ...
- Python-Django 模型层-单表查询
单表操作 -增加,删,改:两种方式:queryset对象的方法,book对象的方法 -改:需要用save() -get()方法:查询的数据有且只有一条,如果多,少,都抛异常 单表查询 -<1&g ...