基础
并发编程模型的分类
共享内存:线程之间共享程序的公共状态,线程之间通过写-度内存中的公共状态来隐式进行通信。同步是指程序用于控制不同 线程之间操作发生相对顺序的机制。在共享内存并发模型里,同步是显示进行的。
消息传递:线程之间没有公共状态,县城之间必须通过明确的发送消息来显示进行通信。在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。
Java并发模型:Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。如果编写多线程程序的Java程序员不理解隐式进行的线程之间同心的工作机制,很可能会遇到各种奇怪的内存可见性问题。
Java内存模型的抽象
作用对象:Java堆内存(实例域、静态域和数组元素)
JMM:通过控制主内存与每个线程的本地内存之间的交互,禁止特定类型的编译器重排序和处理器重排序,来为Java程序员提供内存可见性保证
本地内存:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了改线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它含盖了缓存,写缓冲区,寄存器以及其它的硬件和编译器优化。
重排序
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序
编译器优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
指令级并行的重排序:现代处理器采用了指令级并行技术(ILP)来京多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序
内存系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
内存屏障
写缓冲区:现代的处理器使用写缓冲区来临时保存项内存写入的数据。写缓冲区可以保证指令流水线持续运行,它可以避免由于处理器停顿下来等待项内存写入数据而产生的延迟。同时,通过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对统一内存地址的多次写,可以减少对内存总线的占用。由于写缓冲区进队自己的处理器可见,他会导致处理器执行内存操作的顺序可能与内存实际的操作执行顺序不一致(重排序)。
内存屏障指令:LoadLoad Barriers,StoreStore Barriers,LoadStore Barriers,StoreLoad Barriers
happens-before
在JMM(JSR-133)中,如果一个操作执行的结果需要对另一个操作可见,那么这两个残座之间必须要存在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语义
不管怎么重排序,程序的执行结果不能被改变。编译器和处理器不会对存在数据依赖关系的操作做重排序,该语义把单线程程序保护了起来,为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。
顺序一致性
顺序一致性内存模型
一个线程中的所有操作必须按照程序的顺序来执行
所有线程都只能看到一个单一的操作执行顺序,每个操作都必须院子执行且立刻对所有线程可见
顺序一致性保证
JMM对正确同步的多线程程序的内存一致性做了如下保证:
如果程序是正确同步(synchronized,volatile,final)的,程序的执行将具有顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)
同步程序的顺序一致性效果
临界区内的代码可以重排序
JMM会在退出临界区和进入临界区着两个关键事件点做一些特别处理,是的线程在这两个时间点具有与顺序一致性模型相同的内存视图。这种重排序既提高了执行效率,又没有改变程序的执行结果。
未同步程序的执行特性
对于未同步或未正确同步的多线程程序,JMM只提供最小安全性
JMM不保证但线程内的操作会按程序的顺序执行
JMM不保证所有线程能看到一致的操作执行顺序
JMM不保证对64未的long型和double型变量的读/写操作具有原子性
volatil
特性
可见性:对一个volatil变量的读,总是能看到(任意线程)对这个volatile变量最后的写入
原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种符合操作不具有原子性
volatile写-读建立的happens-before关系
从内存语义的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果(JSR-133)
A线程写一个volatile变量后,B线程读同一个volatile变量。A线程在写volatile变量之前所有课间的共享变量,在B线程读同一个volatile变量后,将立刻变得对B线程可见。
volatile写-读的内存语义
当写一个volatile变量时,JVM会吧改线程对应的本地内存中的共享变量值刷新到主内存
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量
volatile内存语义的实现
volatile重排序规则表
当第二个操作是bolatile写时,不管第一个操作是什么,都不能重排序
当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序
当第一个操作是volatile写,第二个操作是volatile读时,不能重排序
基于保守策略的JMM内存屏障插入策略
StoreStore volatile写 StoreLoad
volatile读 LoadLoad LoadStore