java内存模型中重排序
在了解重排序之前我们先来了解下java多线程之间是怎样对共享数据通信的。
当有多个线程对共享变量访问(读)的时候,我们要保证共享数据的统一就需要有jmm(java内存管理里模型)来进行管
理。比如有一个共享变量,线程A对共享变量更改之后在线程B读到的是线程A更新的值。JMM怎么控制的呢?
当有线程需要访问共享变量的时候,每个线程都会拷贝一份共享共享变量的副本到本地内存,当有线程改变的
共享变量的值时,先会把变量的新改变的值保存在该线程本地内存的副本,然后更新在主内存中共享变量的值,
并更新这个共享变量的版本号。这样当其他线程访问该变量的时候,会先比较版本号,版本号对不上的话就说明
该变量被其他线程更改,这时候线程B就会更新本地内存共享变量的版本号的值。
注:这里说的本地内存并不是真的在物理空间上开辟一块内存,而是为每个线程随机开辟一块内存,用来当作共
享变量的缓冲区。
比如在一个类中声明一个变量类型为volatile。这时候该变量对所有线程可见,但是这样并不是线程安全的,因为
只保证了每个线程读的可见性,当多个线程同时对共享变量进行写操作时,还是不会达到预期的效果。
了解了上面介绍的JMM后,我们再来认识下重排序。
重排序的分类:
1、编译器优化的重排序,在单线程情况下,不改变程序运行的结果,可能会重新安排指令运行的顺序。
2、指令并行的重排序,现代的处理机为了提高cpu的利用效率。会在无数据依赖性(后面会说到这个性质)的情况下,
把对应的指令拆开执行,多少条给一个cpu执行,另外多少条给其他处理机执行。
3、内存系统的重排序,由于处理器使用缓存和读/写缓冲区,这就使得加载和存储操作可能是乱序进行的。我们的cpu
为了提高处理的效率,常常会通过读取缓冲区来处理对应的指令序列。当cpu处理一段写入内存的代码之后,其他cpu
就会处理其他的操作。这样达到了对多核cpu的最大利用效果。
重排序的分析:
所谓重排序,就是当我们写一段代码之后,系统不一定根据我们我们书写的顺序执行,比如我们这样定义了一段代码:
int a=1;int b=2; int c=a+b;
这段代码的执行顺序可能是先执行了int b=2,然后执行int a=1,最后执行c=a+b;这时候数据之间就有了数据的依赖性,
会先得到a和b的值然后才会得到c的值。这样才能保证c的值是正确的。数据依赖性说白了就是指变量之间有没有运算
或者赋值。
你可能会想这样重排序之后可能并不会优化多少。那么来看下面的代码:
int a = 1; //①
int b=2; //②
if(b==2){ //③
c=a*a; //④
}
这时候有四条指令,①和②之间没有关系,所以可能会发生编译器优化的重排序,③和④之间有数据依赖性,所以
③语句运行完之后才会执行语句④,但实际可能是当碰到if的时候,可能会发生指令并行的重排序,先运算出a*a的
值存在缓冲区中,当if判断为真时,直接将缓冲区的值赋给c,大大提高了效率。
无论是哪种重排序都是提高对cpu的利用。毕竟cpu是最宝贵的资源。