什么叫JMM,存在的意义是什么?
这两章看完,大多偏向于理论,主要讲java内存模型,然后通过这个JMM屏蔽了不同操作系统,不同的cpu,内存的访问逻辑,以及硬件本身的一个内存模型的区别。使得java程序可以跑在不同平台。不同的硬件平台有强弱不同的内存模型,有些平台有比较强的内存模型,一致性非常强,都只需要添加少量的内存屏障,当一个线程修改变量,另外一个线程可以直接读取到这个变量。
JSR-133对旧内存模型的修补
JSR-133对JDK 5之前的旧内存模型的修补主要有两个。
·增强volatile的内存语义
。旧内存模型允许volatile变量与普通变量重排序。JSR-133严格
限制volatile变量与普通变量的重排序,使volatile的写-读和锁的释放-获取具有相同的内存语
义。
·增强final的内存语义
。在旧内存模型中,多次读取同一个final变量的值可能会不相同。为
此,JSR-133为final增加了两个重排序规则。在保证final引用不会从构造函数内逸出的情况下,
final具有了初始化安全性
参照这篇文章:JSR 133 (Java Memory Model) FAQ
https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
JMM和重排序是什么关系
Class Reordering {
int x = 0, y = 0;
public void writer() {
x = 1;
y = 2;
}
public void reader() {
int r1 = y;
int r2 = x;
}
}
这段代码在两个线程并发的时候,先调用writer() ,然后在调用reader()方法。假如说在内核1 中x
变量miss,那就会写入cpu内部的写缓冲区,或者请求到下一层的cache中读取。在这个过程当中,可能另一个线程,直接读取到x=0,y=0,赋值给r1,r2 .这个在主存来看,是已经乱序执行了,那假如对writer(),reader()方法加入JMM中sychronized语义,编译器会按照JMM内存模型要求的规则,加入内存屏障的指令,使得先写后读,遵循顺序一致性原则。那就会向串行执行一样。
重排序有哪些? 什么原因会造成重排序?
转载自https://cloud.tencent.com/developer/article/1036747
1 编译器重排序优化
比如尽可能的减少寄存器的读写次数,充分利用局部性。像下面这段代码这样,交替的读x、y,会导致寄存器频繁的交替存储x和y,最糟的情况下寄存器要存储3次x和3次y。如果能让x的一系列操作一块做完,y的一块做完,理想情况下寄存器只需要存储1次x和1次y。
//重排序前
int x = 1;
int y = 2;
int a1 = x * 1;
int b1 = y * 1;
int a2 = x * 2;
int b2 = y * 2;
int a3 = x * 3;
int b3 = y * 3;
//重排序后
int x = 1;
int y = 2;
int a1 = x * 1;
int a2 = x * 2;
int a3 = x * 3;
int b1 = y * 1;
int b2 = y * 2;
int b3 = y * 3;
2 指令重排序
指令重排序是处理器层面做的优化。处理器在执行时往往会因为一些限制而等待,如访存的地址不在cache中发生miss,这时就需要到内存甚至外存去取,然而内存和外区的读取速度比CPU执行速度慢得多。
早期处理器是顺序执行(in-order execution)的,在内存、外存读取数据这段时间,处理器就一直处于等待状态。现在处理器一般都是乱序执行(out-of-order execution),处理器会在等待数据的时候去执行其他已准备好的操作,不会让处理器一直等待。
满足乱序执行的条件:
该缓存的操作数缓存好
有空闲的执行单元
对于下面这段汇编代码,操作1如果发生cache miss,则需要等待读取内存外存。看看有没有能优先执行的指令,操作2依赖于操作1,不能被优先执行,操作3不依赖1和2,所以能优先执行操作3。
所以实际执行顺序是3>1>2
LDR R1, [R0];//操作1
ADD R2, R1, R1;//操作2
ADD R3, R4, R4;//操作3
3 内存系统重排序
由于处理器有读、写缓存区,写缓存区没有及时刷新到内存,造成其他处理器读到的值不是最新的,使得处理器执行的读写操作与内存上反应出的顺序不一致。
如下面这个例子,可能造成处理器A读到的b=0,处理器B读到的a=0。A1写a=1先写到处理器A的写缓存区中,此时内存中a=0。如果这时处理器B从内存中读a,读到的将是0。
以处理器A来说,处理器A执行的顺序是A1>A2>A3,但是由于写缓存区没有及时刷新到内存,所以实际顺序为A2>A1>A3。
初始化:
a = 0;
b = 0;
处理器A执行
a = 1; //A1
read(b); //A2
处理器B执行
b = 2; //B1
read(a); //B2
as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同
步的多线程程序的执行结果不被改变。
网友评论