内存模型基础
线程之间的通信机制有两种:共享内存、消息传递
JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证
三种重排序
- 编译器优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
- 指令级并行的重排序:现代处理器将多条指令重叠执行,若不存在数据依赖,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序
常见的处理器都允许Store-Load重排序,但是都不允许对存在数据依赖的操作重排序。
happens-before
如果一个操作结果需要对另一个操作可见,那么这两个操作之间必须要存在happenes-before关系
happens-before规则:
程序顺序规则:一个县城中的每个操作,happens-before于该线程中的任意后续操作
监视器锁规则:解锁happens-before于加锁
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
传递性:A-b b-c a-c
两个操作之间具有happenes-before关系,并不意味着前一个操作要在后一个操作之前执行,仅仅要求前一个操作对后一个操作可见,且前一个操作按顺序排在第二个操作之前。
重排序
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
数据依赖性:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作就存在数据依赖性
因此,如果两个操作中有一个为写操作,如果这时候把这两个操作进行了重排序,那么就有可能会导致程序执行结果的变化
as-if-serial语义:不管怎么重排序,单线程程序的执行结果不能被改变
在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果
if(flag) ///操作3
{
int i=a*a; ///操作4
}
上面程序操作3和4就是存在控制依赖的两个操作,因此不能随意进行重排序
顺序一致性模型
两大特性:
- 一个线程中的所有操作必须按照程序的顺序来执行
- 所有线程都只能看到一个单一的操作执行顺序,在顺序一致性模型中,每个操作都必须原子执行且立刻对所有线程可见
JMM在具体实现上的基本方针为:在不改变程序执行结果的前提下,尽可能为编译器核处理器的优化大开方便之门。
volatile的内存语义
voaltile变量自身的特性:
- 可见性:对一个volatile变量的读,总是能看到对这个volatile变量最后的写入
- 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性
volatile写和锁的释放有相同的内存语义
volatile读和锁的获取有相同的内存语义
volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存
volatile读的内存语义:JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量
当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序
当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。
JMM选择在每个volatile写的后面插入一个StoreLoad屏障
JMM实现的特点:首先确保正确性,然后再去追求执行效率。
锁的内存语义
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中
当线程获取锁时,JMM会把该线程对应的本地内存置为无效
锁的释放-获取的内存语义的实现至少有下面两种方式:
- 利用volatile变量的写-读具有的内存语义
- 利用CAS所附带的volatile读和voaltile写的内存语义
在concurrent包中的源代码会发现一个通用化的实现模式
- 首先,声明共享变量为volatile
- 使用CAS的原子条件更新来实现线程之间的同步
- 配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信
final域的内存语义
写final域的重排序规则:禁止把final域的写重排序到构造函数之外
该规则可以确保在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了
读final域的重排序规则:初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作
该规则可以确保在读一个对象的final域之前,一定会先读包含这个final域对象的引用
构造函数返回前,被构造对象的引用不能为其他线程所见,因为此时的final域可能还没有被初始化。
happens-before
JMM把happens-before要求禁止的重排序分成了下面两类
- 会改变程序执行结果的重排序---要求编译器和处理器必须禁止这种重排序
- 不会改变程序执行结果的重排序---对编译器和处理器不做要求
双重检查锁定与延迟初始化
在首次发生下列任意一种情况时,一个类或者接口T将立即被初始化
- T是一个类,且一个T类型的实例被创建
- T是一个类,且T中声明的一个静态方法被调用
- T中声明的一个静态字段被赋值
- T中声明的一个静态字段被使用,且这个字段不是一个常量字段
- T是一个顶级类
网友评论