Java内存模型

主内存与工作内存

处理器上的寄存器的读写的速度比内存快几个数量级,为了解决这种速度矛盾,在他们之间加入了高速缓冲。
加入高速缓存带来了一个新的问题:缓存一致性。如果多个缓存共享同一块内存区域,那么多个缓存的数据可能会不一致,需要一些协议来解决这个问题。
所有的变量都存储在主内存中,每个线程还有自己的工作内存,工作内存存储在高速缓存或者寄存器中,保存了该线程使用的变量的主内存副本拷贝。
线程只能直接操作工作内存的变量,不同线程之间的变量值传递需要通过主内存来完成。

内存间交互操作

Java内存模型定义了8个操作来完成主内存和工作内存的交互操作。
markdown

  • read:把一个变量的值从主内存传输到工作内存中
  • load:在read之后执行,把read得到的值放入工作内存的变量副本中
  • use:把工作内存中一个变量的值传递给执行引擎
  • assign:把一个从执行引擎接受到的值赋予工作内存的变量
  • store:把工作内存的一个变量的值传到主内存中
  • write:在store之后执行,把store得到的值放入主内存的变量中
  • lock:作用于主内存的变量
  • unlock

内存模型的三大特性

原子性

Java 内存模型保证了read、load、use、assign、store,write、lock和unlock操作具有原子性,例如对一个int类型的变量执行assign赋值操作,这个操作就是原子性的。但是Java内存模型允许虚拟机将没有被volatile修饰的64位数据(long,double)的读写操作划分为两次32位的操作来进行的。即load,store,read和write操作可以不具备原子性。

可见性

可见性指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取从主内存刷新变量值来实现可见性的。
主要有三种实现可见性的方式。

  • volatile
  • synchronized,对一个变量执行unlock操作之间,必须把变量值同步回主内存。
  • final,被final关键字修饰的字段在构造器中一旦初始化完成,并且没有发生this逃匿(其他线程通过this引用访问到初始化一半的对象),那么其他线程就能看看见final字段的值。

变量使用volatile修饰,不能解决线程不安全问题,因为volatile并不能保证操作的原子性。

有序性

有序性是指:在本线程内观察,所有操作都是有序的。在一个线程观察另外一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在Java内存模型中,允许编译器和处理器对指令进行重新排序,重新排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的争取性。

volatile关键字可以通过添加内存屏障的方式来禁止指令重排,即指令重排序时不能把后面的指令放到内存屏障之前。

也可以通过synchronized来确保有序性,它保证每个时刻只有一个线程执行同步代码,相当于是让线程顺序执行同步代码块。

想知道更深层次的原因,请看大佬写的文章
https://www.jianshu.com/p/16d2762a1d70

https://github.com/CyC2018/CS-Notes
谢谢大佬。

谢谢,爱你么么哒