0%

Java内存模型

Java 内存模型

1. Java内存模型是为了解决什么问题

  • 缓存一致性问题

在现代计算机中,并发的处理任务是一个很常见的场景,但是并发的处理任务,就涉及到处理器和存储设备之间的数据交换,由于处理器运算速度远远的大于存储设备,所以不得不设计了多层次的缓冲,比如磁盘,内存,高速缓存。在程序运行的过程中,处理器需要把数据从内存读到高速缓存中,然后进行运算,之后需要把运算结果再从缓存写到内存中。

处理器高速缓存和内存

而多处理器系统中,每个处理器都有自己的高速缓存,它们又共用一个主存,假如多个处理器的运算任务涉及到同一块主存地址,那么到底哪一个高速缓存的值才是我们想要的呢?这种情况下就可能会带来内存一致性的问题。为了解决这个问题,Java虚拟机规范中定义了一种内存模型,试图屏蔽掉各种硬件和操作系统的内存访问差异,以达到跨平台一致性的效果。

  • 指令重排序问题

为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。因此,如果存在一个计算任务依赖另一个计算任务的中间结果,那么其顺序性并不能靠代码的先后顺序来保证。

2.Java内存模型

Java内存模型的主要目标是定义程序中各个变量的访问规则,规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存,工作内存中保存了该线程用到的变量的主内存副本拷贝,线程不能直接读写主内存中的变量,不同的线程之间也无法访问对方工作内存中的变量,线程之间的变量值传递需要通过主存来完成,他们的关系如下:

Java线程和内存

注意这里和前面的图的区别,前面的图是具体的硬件,这里是对前面的图的一种抽象

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:

  • lock(锁定)
  • unlock(解锁)
  • read(读取)
  • load(载入)
  • use(使用)
  • assign(赋值)
  • store(存储)
  • write(写入)

3. 原子性 可见性和有序性

Java 内存模型主要围绕着在并发过程中如何处理原子性可见性和有序性这3个特征来建立的:

  • 原子性

内存模型规定的八种操作都可以保证原子性。如果应用需要更大范围的原子性,可以使用lock和unlock,体现在代码上就是 synchronized关键字

  • 可见性

可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是:volatile的特殊规则保证了新值能立即同步到主内存,以及每个线程在每次使用volatile变量前都立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。

  • 有序性

Java程序中天然的有序性可以总结为一句话:如果在本地线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的(“指令重排序”现象和“线程工作内存与主内存同步延迟”现象)。

Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性:

  • volatile关键字本身就包含了禁止指令重排序的语义
  • synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入

4. 对于volatile变量的特殊规则

volatile变量的两个语义:

  • 保证此变量对所有线程的可见性

这里的可见性是指当一条线程修改了这个变量的值,新值能立即同步到主内存,以及每个线程在每次使用volatile变量前都立即从主内存刷新。但是可见性并不能完全保证volatile变量就是完全同步的。比如我们在A,B两个个线程中分别对一个 volatile变量做++的操作,假如这个变量的值是1,AB同时读到都是1,A线程做了++操作变成了2写入到主内存中,B可能之前已经从主存读了这个1的值也在++,然后得到2,最后结果是执行了两次++操作,但是得到了2

  • 禁止指令重排序优化

主要是通过 volatile变量在赋值后,增加一句内存屏障的指令来实现的,在做重排序的时候,不能把后面的指令重排序到内存屏障之前的位置。