Java内存可见性
# 1. 原子操作的实现原理
# 1.1 处理器实现
- 使用总线锁 当一个处理器需要对某个共享变量进行操作时,会在总线上传输**LOCK #**信号,使得其他的处理器请求被阻塞,从而实现独占共享内存
- 使用缓存锁定 指内存区域如果被缓存在处理器的缓存行中,并且在LOCK期间被锁定,那么当它执行锁操作回写到内存时,处理器修改内部的内存地址,并允许它的缓存一致性机制来保证操作原子性,因为缓存一致性会同时修改两个以上处理器缓存的内存区域,当其他处理器回写已被锁定的缓存行数据,会使缓存行失效
缓存锁定失效
① 操作数据不能被缓存或者操作数据跨多个缓存行时(32位机器)
② 有些处理器不支持缓存锁定
# 1.2 Java实现
CAS
Compare and Swap,CAS操作需要输出两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间需要比较旧值有没有发生变化,未发生变化,才进行交换
- 循环CAS实现:循环进行CAS操作直到成功为止,但循环时间开销大,对CPU不友好,同时只能保证一个共享变量的原子操作(或者可以将多个共享变量合成一个)
当出现 “ABA” 问题时,可以通过在对变量添加一个版本号来解决
- 使用锁机制实现:保证只有获得锁的进程才能操作锁定的内存区域
上述为第二章的核心内容,下面的内容为第三章的知识
# 2. 重排序
提示
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重排序的一种手段
- 编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序
这里的数据依赖仅针对单个处理器和单个线程
- as if serial:不管如何重排序,单线程程序的执行结果不能被改变
# 3. Volatile内存语义
简单的说就是对volatile变量的单个读/写看成使用一个锁对这些操作进行同步
写-读的内存语义
- 当(线程A)写一个volatile变量时,Java内存模型 (JMM) 会把该线程对应的本地内存中的共享变量刷新到主内存
- 当(线程B)读一个volatile变量,JMM会把该线程对应的本地内存置为无效,线程将从主内存中读取共享变量
JMM为了实现volatile内存语义,会限制上述提到的重排序
# 4. 锁的内存语义
锁的释放和获取的内存语义
- 当线程释放锁,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中
- 当线程获取锁,JMM会把该线程对应的本地内存置为无效,从而使得临界区代码必须从主内存中读取共享变量
# 5. final的内存语义
- 写规则:JMM禁止编译器把final域的写重排序到构造函数之外,从而确保在对象引用被任意线程可见之前,该对象的final域已经被正确初始化了
- 读规则:初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作不能重排序
若final域为引用类型(数组),在构造函数内对一个final引用的对象成员域的写入,与随后在构造函数之外把这个被构造对象的引用赋值给一个变量,不能重排序
# 6. happens-before
定义
① 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序在前
② 两个操作之间存在happens-before关系,并不意味Java平台的具体实现必须这样。如果重排序之后的执行结果,与按happens-before执行的结果一致,这种重排序被允许
as-if-serial保证单线程内程序的执行结果不改变,happens-before保证正确同步的多线程程序执行结果不变 在程序员看来,程序是按照as-if-serial和happens-before的顺序执行的
happens-before规则:
- 程序顺序规则:一个线程中的每个操作,happens-before与该线程中的任意后续操作
- 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
- Volatile变量规则:对一个Volatile域的写,happens-before于任意后续对这个Volatile域的读
- 传递性:如果A happens-before B,B happens-before C,那么A happens-before C
# 7. 双重检查锁定与延迟初始化
笔记
在多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销
# 7.1 基于volatile的解决方案
双重检查锁定的缺陷:
memory = allocate(); //1: 分配对象的内存空间
ctorInstance(memory); //2: 初始化对象
instance = memory; //3. 设置instance指向被分配的内存地址
2
3
若果2和3的顺序被重排序,一旦多线程执行,其他线程可能会访问到一个还未初始化的对象
将需要创建的对象声明为volatile变量,因为volatile会限制重排序
# 7.2 基于类初始化的解决方案
JVM在类的初始化阶段(即在Class被加载后,且在被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会获取一个锁,保证同步多个线程对同一个类的初始化
# 8. JMM的内存可见性保证
- 单线程程序:不会出现内存可见性问题,编译器,runtime和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同
- 正确同步的多线程程序:正确同步的多线程程序的执行将具有顺序一致性,JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性保证
- 未同步/未正确同步的多线程程序:JMM为它们提供了最小安全性保障:线程执行时读到的值,要么是之前某个线程写入的值(未必是完整有效的),要么是默认值(0、null、false)