赞
踩
Java 是最早尝试提供内存模型的编程语言。
一般来说,编程语言也可以直接复用操作系统层面的内存模型。不过,不同的操作系统内存模型不同。如果直接复用操作系统层面的内存模型,就可能会导致同样一套代码换了一个操作系统就无法执行了。Java 语言是跨平台的,它需要自己提供一套内存模型以屏蔽系统差异。
Java内存模型(JMM,Java Memory Model) ,是Java 定义的并发编程相关的一组规范,除了抽象了线程和主内存之间的关系之外,其还规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范,其主要目的是为了简化多线程编程,增强程序可移植性的。
为什么要遵守这些并发相关的原则和规范呢? 这是因为并发编程下,像 CPU 多级缓存和指令重排这类设计可能会导致程序运行出现一些问题。为此,JMM 抽象了 happens-before 原则(后文会详细介绍到)来解决这个指令重排序问题。
JMM 说白了就是定义了一些规范来解决这些问题,开发发者可以利用这些规范更方便地开发多线程程序。 对于 Java 开发者说,你不需要了解底层原理,直接使用并发相关的一些关键字和类(比如 volatile
、synchronized
、各种 Lock
)即可开发出并发安全的程序。
首先介绍一下什么是主内存?什么是本地内存?
Java 内存模型(JMM) 抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中。
Java 内存模型的抽象示意图如下:
从上图来看,线程 1 与线程 2 之间如果要进行通信的话,必须要经历下面 2 个步骤:
线程 1 把本地内存中修改过的共享变量副本的值同步到主内存中去。
线程 2 到主存中读取对应的共享变量的值。
也就是说,JMM 为共享变量提供了可见性的保障。
不过,多线程下,对主内存中的一个共享变量进行操作有可能诱发线程安全问题。举个例子:
线程 1 和线程 2 分别对同一个共享变量进行操作,一个执行修改,一个执行读取。
线程 2 读取到的是线程 1 修改之前的值还是修改后的值并不确定,都有可能,因为线程 1 和线程 2 都是先将共享变量从主内存拷贝到对应线程的工作内存中。
关于主内存与工作内存直接的具体交互协议,即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步到主内存之间的实现细节,Java 内存模型定义了以下八种同步操作:
除了这 8 种同步操作之外,还规定了下面这些同步规则来保证这些同步操作的正确执行:
这是一个比较常见的问题,很多初学者非常容易搞混。 Java 内存区域和内存模型是完全不一样的两个东西 :
前面说到JMM指定了一组排序规则,来保证线程之间的可见性。这一组规则即被称为 happens-before。
JMM规定,要想保证B操作能够看到A操作的结果(无论他们是否在同一个线程),那么A和B之间必须满足happens-before关系。
JSR 133 引入了 happens-before 这个概念来描述两个操作之间的内存可见性。
为什么需要 happens-before 原则? happens-before 原则的诞生是为了程序员和编译器、处理器之间的平衡。程序员追求的是易于理解和编程的强内存模型,遵守既定规则编码即可。编译器和处理器追求的是较少约束的弱内存模型,让它们尽己所能地去优化性能,让性能最大化。
happens-before 原则的设计思想:
JSR-133 对 happens-before 原则的定义:
可以看下面这个例子:
int userNum = getUserNum(); // 1
int teacherNum = getTeacherNum(); // 2
int totalNum = userNum + teacherNum; // 3
happens-before 原则表达的意义其实并不是一个操作发生在另外一个操作的前面,虽然这从程序员的角度上来说也并无大碍。更准确地来说,它更想表达的意义是前一个操作的结果对于后一个操作是可见的,无论这两个操作是否在同一个线程里。
举个例子:操作 1 happens-before 操作 2,即使操作 1 和操作 2 不在同一个线程内,JMM 也会保证操作 1 的结果对操作 2 是可见的。
happens-before 常见规则有哪些?
happens-before 的规则有 8 条,重点讲解下面列举的 6 条:
如果两个操作不满足上述任意一个 happens-before 规则,那么这两个操作就没有顺序的保障,JVM 可以对这两个操作进行重排序。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。