java 内存模型

内存模型

  • 一些处理器内存模型比较强大,表现为其所有处理器始终在任何给定的内存位置看到完全相同的值;
  • 其他处理器的内存模型表现的比较弱,需要通过特殊的称为内存屏障(memory barriers)的指令来刷新缓存,以便对缓存的写入对其他处理器可见,或使本地处理器高速缓存无效,以便重新获取其他处理器写入的缓存。这些内存屏障通常在执行锁定和解锁操作的时候执行,对于高级语言来说,它们是不可见的。后面在讲解Java内存模型的时候会专门介绍下内存屏障。
    Java语言提供了volatile, final, 和 synchronized 旨在帮助程序员向编译器描述程序的并发要求。
    Java内存模型定义了volatile和synchronized的行为,更重要的是,确保做了正确同步的Java程序可以在所有的处理器体系结构上正确运行。

Java内存模型主要参与者:

  • 变量:这里的变量,主要指实例字段、类变量,以及数组中的对象元素,不包括局部变量和方法参数(线程私有);
  • 主内存:共享的主存储器,变量保存在这里;因为一个线程不可能访问另一个线程的参数和局部变量,所以将局部变量视为驻留在共享主存储器或者工作内存里面都没有关系;
  • 工作内存:每个线程都有一个工作内存,在其中保留了自己必须使用或分配的变量的工作副本。线程执行的时候,将对这些工作副本继续操作。主内存包含每个变量的主副本。对于何时允许或要求线程将其变量的工作副本的内容传输到主副本存在一些规则,反之亦然;
  • Java线程:后面介绍Java线程的文章会详细讲解。

java 内存模型

java 内存模型定义了程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
此处的变量与java代码中的变量略有区别,此处变量是指实例字段,静态字段和构成数组对象的元素。不包括局部变量和方法参数,因为后者是线程私有的,不存在竞争问题。

java内存模型规定了所有变量都必须存储在主内存中(此处主内存可类比物理机主内存,此处只是虚拟机内存的一部分被定义为主内存),每个线程有自己的工作内存(可与物理机的高速缓存相比),线程对变量的所有操作都必须在工作内存中进行,线程中变量是主内存的拷贝,不同线程之间无法直接访问对方工作内存,线程间变量的传递均需要通过主内存来完成

volatile

volatile是JVM最轻量级的同步机制。

使用了volatile之后,能够保证新值能够立即同步到主内存,以及每次使用前立即从主内存刷新。

关键字会保证可见性和有序性,保证不了原子性
volatile关键字的作用很简单,就是一个线程在对主内存的某一份数据进行更改时,改完之后会立刻刷新到主内存。并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。这样一来就保证了可见性

volatile关键字使用要点

仅适用于变量;
保证变量值总是从主内存中读取,而不是从Thread的本地缓存,也就是工作内存;
使用volatile关键字声明的操作不一定都是原子的,取决于编译出来的汇编指令;
除了long和double类型,即使不使用volatile关键字,原始类型变量读和写都具有可见性;
如果一个变量没有在多线程之间共享,则不需要对变量使用volatile关键字;
volatile变量访问永远不会有阻塞机会,因为我们只进行简单的读取和写入操作,不会保持任何锁或等待任何锁。

同步操作Synchronized

Synchronized底层实现

通过使用同步,可以实现任意大语句块的原子性单位,使我们能够解决volatile无法实现的read-modify-write问题。
synchronized块最终变为了由monitorenter和monitorexit包裹的反汇编指令语句块。
monitorenter:操作对象是一个reference对象,每个对象都与一个监视器关联,如果有其他线程获取了这个对象的monitor,当前的线程就要等待。每个对象的监视器有一个objectref条目计数器对象,成功进入监视器之后,监视器的objectref+1,然后,该线程就成为监视器的所有者了。
同一个线程重复执行monitorenter,会重新进入监视器,并且objectref+1。
monitorexit:操作对象是一个reference对象,执行该指令,objectref-1,直到objectref=0的时候,线程退出监视器,不再是对象所有者。

synchronized总结

synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。
不过两者的本质都是对对象监视器 monitor 的获取。

Synchronized如何实现可见性

Synchronized确保以可运行的方式使线程在同步块之前或者期间对内存的写入对于监视同一个对象的其他线程可见。

  • 执行了monitorenter之后,释放监视器,并且将工作内存刷新到主内存中,以便该线程进行的写入对其他线程可见;
  • 在进入同步块之前,我们先要先执行monitorenter,使得当前线程的工作内存无效化,以便从主内存中重新加载变量。

final

Java中使用final字段的时候,JVM保证对象的使用者仅在构造完成后才能看到该字段的最终值。
为了达到这个目的,JVM会在final对象构造函数的末尾引入冻结操作,该操作可以防止对构造函数进行任何后续操作,或者进行指令重排。

你可能感兴趣的:(java 内存模型)