一、重排序

好处:重排序可以提升性能,避免在一个耗时很长的指令在“执行”阶段呆很长时间,而导致后续的指令都卡在“执行”之前的阶段上。

坏处:重排序对多线程的影响

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 中的 重排序、主存、原子操作的更多相关文章

  1. JS中数组重排序方法

    在数组中有两个可以用来直接排序的方法,分别是reverse()和sort().下面通过本文给大家详细介绍,对js数组重排序相关知识感兴趣的朋友一起看看吧 1.数组中已存在两个可直接用来重排序的方法:r ...

  2. JMM中的重排序及内存屏障

    目录 1. 概述 2. 重排序 2-1. as-if-serial语义 2-2. 重排序的种类 2-3. 从Java源代码到最终实际执行的指令序列, 会分别经历下面3中重排序. 3. 内存屏障类型 3 ...

  3. Javascript中数组重排序方法详解

    在数组中有两个可以用来直接排序的方法,分别是reverse()和sort().下面通过本文给大家详细介绍,对js 数组重排序相关知识感兴趣的朋友一起看看吧. 1.数组中已存在两个可直接用来重排序的方法 ...

  4. JVM的重排序

    重排序一般是编译器或执行时环境为了优化程序性能而採取的对指令进行又一次排序执行的一种手段.重排序分为两类:编译期重排序和执行期重排序,分别相应编译时和执行时环境. 在并发程序中,程序猿会特别关注不同进 ...

  5. JVM学习(八)指令重排序

    一.数据依赖性 在学习JVM的指令重排序之前,我们先了解一下什么是数据依赖性: 编译器和处理器在处理具体的指令时,可能会对操作进行重排序来提高执行性能[多条指令并行执行,所以提升性能的同时也可能会导致 ...

  6. volotile关键字的内存可见性及重排序

    在理解volotile关键字的作用之前,先粗略解释下内存可见性与指令重排序. 1. 内存可见性 Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,并且线程 ...

  7. java基础之 重排序

    重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段.重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境. 在并发程序中,程序员会特别关注不同进程 ...

  8. Java内存访问重排序笔记

    >>关于重排序 重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段. 重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境. > ...

  9. JVM并发机制的探讨——内存模型、内存可见性和指令重排序

    并发本来就是个有意思的问题,尤其是现在又流行这么一句话:“高帅富加机器,穷矮搓搞优化”. 从这句话可以看到,无论是高帅富还是穷矮搓都需要深入理解并发编程,高帅富加多了机器,需要协调多台机器或者多个CP ...

随机推荐

  1. js 数字前自动补零

    num为传入的数字,n为需要的字符长度 return (Array(n).join(0) + num).slice(-n); 例如 我想返回两位数  输入6 然后返回06 就可以这样写: return ...

  2. artDialog记录

    //在子页面加按钮的方式 var api = frameElement.api, W = api.opener; api.button({ id: 'valueOk', name: '确定', cal ...

  3. 牛牛与数组 (简单dp)

    题目链接 这种题一看就是dp啊,dp[i][j]表示第i位放j的方案数,转移方程为dp[i][j]=dp[i-1][k]{k<=i||k%i!=0},当然我们可以三层循环来找,但数据显然会超时, ...

  4. Java并发之Thread类的使用

    一.线程的几种状态 线程从创建到最终的消亡,要经历若干个状态.一般来说,线程包括以下这几个状态:创建(new).就绪(runnable).运行(running).阻塞(blocked).time wa ...

  5. 409 javascript if and while表达式

    数组定义.特点.运算符:算术运算 ++ --(自减 自加) 赋值运算发 =比较:!= == === 逻辑运算 有 && || ! 正则表达式 修饰符 i:用来表示 g:很少演示(在第一 ...

  6. springboot集成elasticsearch遇到的问题

    public interface EsBlogRepository extends ElasticsearchRepository<EsBlog,String>{ Page<EsBl ...

  7. Reveal 使用详解

    Reveal是一款调试iOS程序UI界面的神器 官网:https://revealapp.com 下载:https://revealapp.com/download/ 建议下载至少Reveal4版本, ...

  8. echarts tree 树型图层级距离设置

    网上找了半天,没有找到设置层级距离的属性,默认是自动适应的,无奈只能改源码,分享出来希望可以帮到有相同需求的... 上github下载echarts源码包,打开src=>chart=>tr ...

  9. 【python】__import__

    函数定义 __import__(name, globals={}, locals={}, fromlist=[], level=-1) -> module Import a module. Be ...

  10. sass—使用自定义function和@each实现栅格布局

    /*使用自定义function和@each实现栅格布局*/ @function buildLayout($num: 5){ $map: (defaultValue: 0); //不能直接生成col,需 ...