java内存模型
CPU工作原理:
-
单核CPU工作原理-高速缓存:
![image-20210512080222407]()
- cpu读取数据时按照L1,L2,L3,物理内存的顺序依次查找
- L1,L2,L3的速度依次递减,容量依次递增
-
多核CPU工作原理-高速缓存:
![image-20210512080411417]()
并发编程问题:
-
缓存一致性问题:多核多线程并行执行导致L1,L2缓存数据不一致
![image-20210512080648835]()
-
举个栗子:
![image-20210512081731384]()
结果分析:
![image-20210512081848227]()
-
编译器优化和指令重排:

上述1属于编译器重排序,2,3属于处理器重排序。重排序会导致多线程程序出现内存可见性问题。
举个栗子-重排序:执行完成后 i = 1或者0
public class CpuReOrder {
private int a = 0;
private boolean flag = false;
public void write(){
a = 1; //1
flag = true; //2
}
public void read(){
if(flag){ //3
int i = a; //4
System.out.println(i);
}
}
public static void main(String[] args){
final CpuReOrder cpuReOrder = new CpuReOrder();
//写线程
new Thread(()->cpuReOrder.write()).start();
//读线程
new Thread(()->cpuReOrder.read()).start();
}
}
java内存模型
-
重排序-数据依赖性:
![image-20210512083136999]()
![image-20210512084419779]()
![img]()
-
As-if-serial:
![image-20210512084750078]()
![image-20210512084827950]()
并发编程的问题:
-
原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
- x=10; y=x; x++; x=x+1;x=new x(); 哪些操作具有原子性?
-
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。(volatile,锁)
-
有序性即程序执行的顺序按照代码的先后顺序执行。(happens-before)
原子性、可见性、有序性是一个抽象的概念。其底层问题就是前面提到的硬件层面的缓存一致性问题、处理器优化问题和指令重排问题。
缓存一致性问题其实就是可见性问题。而处理器优化是可以导致原子性问题的(指令优化执行)。指令重排即会导致有序性问题。
举个栗子:执行结束后:count=?
![image-20210512085306133]()
内存模型
-
计算机内存模型:为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。它解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。
-
内存屏障:每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取。但是这样的弊端也很明显:不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并不是一样,java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。
- 硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。内存屏障有两个作用:阻止屏障两侧的指令重排序;强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
- 对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存加载数据;对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存, 让其他线程可见。
-
**java内存屏障:**java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是Load,store两种的组合,完成一系列的屏障和数据同步功能。
- LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
- StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
- LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
- StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。 它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
-
数据竞争:
![image-20210512161055987]()
-
顺序一致性内存模型:
![image-20210512161252976]()
-
顺序一致性模型中程序执行示意图
![image-20210512161357355]()
正确同步的程序:
![image-20210513073807001]()
JMM与顺序一致性模型执行效果对比:
![image-20210513073843244]()
-
**happens before规则:**在JMM中要保证一个操作的结果对另一个操作可见,那么这两个操作(可能是多个线程)之间要存在happens-before关系。
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而
且第一个操作的执行顺序排在第二个操作之前。 - 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens- before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而
-
happens-before 具体规则:
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续的操作。
- 监视器锁的规则:锁的解锁happens-before随后对它的加锁。
- volatile变量的规则:对一个volatile域的写happens-before于任意后续对它的读。
- 传递性:A happens-before B, B happens-before C,那么A happens-before C。
- start()规则:线程A执行操作ThreadB.start(),那么A线程的ThreadB.start()操作happens-before 于B线程的任意操作。这意味着:线程A在执行ThreadB.start()之前对共享变量所做的修改,在线程B执行后都将对B可见。
- join()规则:线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before 于线程A从ThreadB.join()操作成功返回。这意味着:线程A执行操作ThreadB.join()并成功返回后,线程B的任意操作都将对线程A可见。
举个栗子:
![image-20210513075624617]()
![image-20210513075716862]()
-
Volatile内存语义总结:
![image-20210513090021565]()
-
Volatile内存语义实现-JMM针对编译器指定的重排序规则表
![2]()
-
Volatile内存语义实现
![image-20210513091727487]()
![image-20210513091737422]()
-
锁的释放获取建立的happens-before原则
![1]()
-
JMM的内存可见性保证
![image-20210513093736642]()

























浙公网安备 33010602011771号