Montior 锁
- 通过插入 monitorenter/monitorexit 来实现同步块
- 锁存在 Object Header 里的 Mark Word 上。Mark Word 无锁状态下存储分代信息,hashCode等。Mark Word 上2bit 的锁标志,表示当前的锁的类型
偏向锁
在mark word 中存储指向持有锁的线程的 id。
获取:
- 检查mark word 中的thead id 是否为自己,是则已经持有锁。
- 否则如果是处在可偏向状态,则尝试CAS 设置thread id
- 如果CAS 失败,则需要等到全局安全点,尝试撤销锁。检查持有锁的线程,将等待线程升级为轻量锁然后唤醒,或者如果持有锁的线程已经不再或有则尝试重新偏向。
轻量级锁
把mark word记录到自己的栈上,然后将mark word 替换为指向这条记录的指针。
- 复制mark word 到栈上
- 尝试CAS 替换mark word 为指向栈上记录的指针,如成功则成功加锁
- 如果失败,则尝试自旋一段时间,任然无法获取则膨胀微重量级锁
- 获取所得线程解锁时,唤醒对应的重量级锁
重量级锁
使用操作系统的锁。mark word 被替换为指向mutex 的指针。使用 pthread_cond_wait/pthread_cond_signal 来等待/唤醒线程
内存顺序
指令实际的内存执行顺序可以被三个因素影响
- 编译器优化重排序
- CPU 优化重排序
- CPU 缓存导致的重排序
对应阻止重排序的方法
遵循Java 的Happens-Before内存顺序模型
- 编译器。不重排序有前后依赖的指令。
- CPU。插入内存屏障
Happens before 模型
A happens before B 指。A 的内存操作对于B 可见
- 同一线程中,指令A 在 B 前面,则A happends before B
- 锁的获取happens before 锁的释放
- 一个voliate写happens before 另一个voliate读
- Thread A 里 start Thread B. 则 threadB.start() happens before 任意 thread 中的操作。
- Thread B 中的操作 happens before threadB.join()
- 遵循传递率。A happens before B, B happens before C 则 A happens before C
CPU 内存屏障
- LoadLoad
- LoadStore
- StoreStore
- StoreLoad
Volatile
内存语义
- volatile 写使得线程对应本地内存的共享变量被刷回主内存。
- volatile 读使得本地内存的共享变量无效,必须从主内存中读取。
实现方法
编译器层面
禁止volatile 操作和其他指令的重排
- volatile读 当前一个操作为volatile读时,禁止与后续操作重排
- volatile写 当后一个操作为volatile写时,禁止与前面的操作重排
CPU层面
插入内存屏障
- volatile读 在其后面插入 loadload, loadStore 屏障。使volatile读立即对接下来的操作可见
- volatile写 在前面插入 storestore, 后面加入storeload。使得volatile写之钱的写操作都可见。同时不会与后面可能的volatile读操作重排。
Final
对final 的赋值不会溢出 constructor。保证外界不会观察到final的值得变化。
例子
Double-Checked Locking (DCL)
错误原因,对共享变量赋值的部分,和检查获取共享变量的部分,没有同步。
instance = new Foo()
并非atmoic,需要
- 分配内存
- 使用constructor初始化
- 赋值给instance
step2, 3 可能被重排,导致其他线程获取到未初始化的实例
Solutions
- volatile instance。禁止重排,保证可见
- 将初始化代码移动到 class static block 中。