Java内存模型

Smile_slime_47

概述

Java内存模型(Java Memory Model)是为了避免不同操作系统之间内存模型的不同,从而导致多线程工作时的结果不一致问题。

一方面来说,由于不同CPU的Cache结构不同导致操作系统的内存模型不同,另一方面,操作系统会对代码进行指令重排序,但是实际上这个操作无法保证在多线程情况下的正确性(即,实际执行代码的顺序并不一定是我们编写代码的顺序);因此需要Java定义一套规范来来保证并发编程的可行性,让开发者无需去顾及不同平台导致的并发特性的不同。

主内存和本地内存

所有线程创建的实例对象及其成员字段都存储在主内存中,而方法中的局部变量引用则存储在线程栈中。

当线程之间需要进行通信,即对同一个共享变量进行操作时,我们规定所有的共享变量都存储在主内存中(实际操作的共享变量往往都是类的某个成员字段),而每个线程都有一个虚拟的,并不存在的本地内存,在需要操作时,线程先将主内存中的共享变量复制一份副本到本地内存,并对本地内存上的副本进行修改,修改完毕后再将副本更新到主内存上的共享变量。

这一点保证了线程之间共享变量的可见性,但是也导致了一个问题:线程B读取到的共享变量无法确定是线程A修改前还是修改后的

Happens-Before原则

JMM的正确运行是基于一系列Happens-Before原则的

Happens-Before原则指明了一系列可以被重排序的操作和一系列不能被重排序的操作,如果我们说操作A happens-before 操作B,那么操作A的结果就是对操作B可见的,如果指令重排序无法保证这一点,那么JMM就会禁止这个操作。

举例来说,线程A的某一个操作如果happens-before线程B的某一个操作,那么JMM就会保证线程A的这一步操作一定是对于线程B可见的(通常是先执行)

happens-before规则一共有八条,其中常用的有五条

  • 程序顺序规则:一个线程内,按照代码顺序,书写在前面的操作 happens-before 于书写在后面的操作
  • 解锁规则:解锁 happens-before 于加锁
  • volatile变量规则:对一个 volatile 变量的写操作 happens-before 于后面对这个 volatile 变量的读操作。说白了就是对 volatile 变量的写操作的结果对于发生于其后的任何操作都是可见的
  • 传递规则:如果 A happens-before B,且 B happens-before C,那么 A happens-before C
  • 线程启动规则:Thread 对象的 start()方法 happens-before 于此线程的每一个动作

对于程序员来说:他们只负责实现了高效的Java并发代码编程部分,而保证了Java并发代码的正确运行是由JMM实现的,它基于一系列happens-before规则,对于会影响并发正确性的重排序规则予以禁止,对于无伤大雅的重排序优化则允许

并发特性的实现

原子性

一组具有原子性的操作,必须一次性执行完毕而不被中断,要么就都不执行。在Java中原子性由synchronized关键字和各种Lock锁实现的

而原子类是基于CAS算法保证了原子操作的正确性

可见性

如果一个线程修改了某个共享变量,那么其他线程应当立即看到共享变量的最新值,但是由于JMM模型的特性,这一点并不能得到保证

因此Java需要通过volatile关键字来实现,这个关键字告诉线程该变量应当越过本地内存,直接去操作主内存中的共享变量

有序性

基于上述happen-before中的volatile变量规则,可以通过对共享变量声明为volatile来保证读写操作的有序性,而不会被重排序打乱

Comments