内存模型基础
并发模型的两个关键问题
在并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步。
通信是指线程之间以何种机制来交换信息。在命令是编程中,线程之间的通信机制由了两种:共享内存和消息传递。
在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。在消息传递的并发模型里,线程之间没有公告状态,线程之间必须通过发送消息来显示进行通信。
同步是指程序中用于控制不同线程间操作发生相对顺序的机制。在共享内存并发模型里,同步时显示进行的。程序员必须显示指定某个方法或某段代码需要在线程之间互斥执行。在消息传递的并发模型里,由于消息的发送必须在消息的接受之前,因此同步时隐式进行的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
Java的并发采用的时共享内存模型,Java线程之间的通信总是隐式进行的,整个通信过程必须显示的指定。
Java内存模型的抽象概念
在java中,所有的实例域,静态域和数组元素都存储在堆内存中,对内存在线程之间共享。局部变量,方法定义参数和异常处理器参数不会在线程之间共享。
Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。
JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都会有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。
内存屏障类型
为了保证内存可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型处理器重排序,
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad Barriers | Load1;LoadLoad;Load2 | 确保Load1数据的装载先于Load2及所有后续装在命令的装载 |
StoreStore Barriers | Store1;StoreStore;Store2 | 确保Store1数据对于其他处理可见(刷新到内存)先于Store2及所有后续存储指令的存储 |
LoadStroe Barriers | Load1;LoadStroe;Stroe2 | 确保Load1数据装载优先于Stroe2及所有后续的存储指令刷新到内存 |
StroeLoad Barriers | Stroe1;StroeLoad;Load2 | 确保Stroe1数据对其他处理器变得可见优先于Load2及所有后续装载指令的装载。StroeLoad Barriers 会事该屏障之前所有内存访问指令完成之后,才执行该屏障之后的内存访问指令 |
happens-before
从JDK 5 开始,Java使用心得JSR-133内存模型。JSR-133使用happens-berore的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作就存在Happens-before关系。
程序顺序规则:一个线程中的每个操作,Happens-before于该线程中的任意后续操作
监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
volatile变量规则:对一个volatile域的写,happens-before 于任意后续对这个volatile域的读。
传递性:如果A happens-before B,且B happens-before C ,那么A happens-before C
重排序
重排序是指编译器和处理器为了优化程序性能而对指令序列重新排序的一种手段
as-if-serial
as-if-serial的意识:不管怎样重排序,线程的执行结果是不能被改变的。编译器,runtime和处理器都必须遵守as-if-serial。
参考书籍:
《Java并发编程的艺术》