Java内存模型
一.Java内存模型基础
并发编程中的两个关键问题:线程之间如何通信及线程之间如何同步
线程之间的通信机制:共享内存和消息传递,JAVA采用的是共享内存模型。
JAVA内存模型的抽象结构:
Java内存模型是围绕着并发编程中原子性、可见性、有序性这三个特征来建立的。
- 原子性:原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行.
- 可见性:可见性就是指当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改.
- 有序性:有序性即程序执行的顺序按照代码的先后顺序执行。
二.重排序
概述:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,但是,重排序可能会导致程序执行的结果不是我们需要的结果!因此,就需要我们通过“volatile,synchronize,锁等方式”作出正确的实现同步。
思考:
- 重排序会造成什么问题?
- 如何对待重排序现象?
操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序,重排序对单线程没有影响,但是对多线程可能会隐患,
重排序要遵守的三条原则:
1.数据依赖性
概述:
定义:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性
分为三类:写后读,写后写,读后写
编译器和处理器在重排序时,会遵
守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序
数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作
2.af-if-serial语义
含义:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变
sif-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题
3.程序顺序规则
4.重排序的影响
概述:在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是as-if-serial
语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中,对存在控制依赖的操
作重排序,可能会改变程序的执行结果。
三.顺序一致性
概述:顺序一致性内存模型是一个理论参考模型,在设计的时候,处理器的内存模型和编程语
言的内存模型都会以顺序一致性内存模型作为参照。
概念:程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同
1.数据竞争与顺序一致性
2.顺序一致性内存模型
概述:为程序员提供了极强的内存可见性保证,但是,这种模型实际上阻止了硬件或者编译器对程序代码进行的大部分优化操作。为此,人们提出了很多松弛的(relaxed)内存顺序模型。
规定了两件事:
1. 一个线程中的所有操作必须按照程序的顺序来执行。
2. (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见
JMM在具体实现上的基本方针为:在不改变(正确同步的)程序执行结果的前提下,尽可能地为编译器和处理器的优化打开方便之门
3.同步程序的顺序一致性效果
4.未同步程序的执行特性
Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性
四.volatile的内存语义
语义:
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
- 禁止进行指令重排序
实现:
锁是Java并发编程中最重要的同步机制
五.锁的内存语言
- 让临界区互斥执行
- 让释放锁的线程向获取同一个锁的线程发送消息
六.final的内存语言
阐述操作之间的内存可见性
七.happens-before
概述:
概念:”先行发生”。
Java内存模型中的八条可保证happen—before的规则:
1.程序次序规则:在一个单独的线程中,按照程序代码的执行流顺序,(时间上)先执行的操作happen—before(时间上)后执行的操作。
2.管理锁定规则:一个unlock操作happen—before后面(时间上的先后顺序,下同)对同一个锁的lock操作。
3.volatile变量规则:对一个volatile变量的写操作happen—before后面对该变量的读操作。
4.线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。
5.线程终止规则:线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
6.线程中断规则:对线程interrupt()方法的调用happen—before发生于被中断线程的代码检测到中断时事件的发生。
7.对象终结规则:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。
8.传递性:如果操作A happen—before操作B,操作B happen—before操作C,那么可以得出A happen—before操作C。
happen-before与jvm的关系:
注意:两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个
操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见
解读:一个happens-before规则对应于一个或多个编译器和处理器重排序规则。
八.双重检查锁定与延迟初始化
在JAVA多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销,双重检查锁定是常见的延迟初始化技术
思考
- 为什么需要延迟初始化
- 双重检查锁定的实现
- 跟内存模型的关系