Volatile
- 确保可见性,比 synchronized 更加轻量级,不会引起线程上下文切换和调度。
- 具体实现:Lock 前缀的汇编代码指令。
- 将当前处理器缓存的数据写回到系统内存,锁缓存而非锁总线。
- 通过嗅探确保缓存一致性,回写操作致使其他处理器缓存无效
- 使用优化:将共享变量追加到 64 字节,避免头节点和尾节点加载到同一个缓存行而互相锁定。
- 部分追加字节方式可能不生效,Java 7 之后会淘汰无用字段。
Synchronized
- Java 中每一个对象都可以作为锁。
- 普通方法,锁是当前实例对象。
- 静态方法,锁是当前类的 Class 对象。
- 方法块,锁是 Synchronized 括号里配置的对象。
- 锁消除和锁粗化。
- 同步块的实现使用了 monitorenter 指令和 monitorexit 指令,同步方法则是依靠方法修饰符 ACC_SYNCHRONIZED,本质上是对一个对象的监视器(monitor)进行获取,且该获取过程是排他的。
- Mark Word:默认存储对象的 hashcode、分代年龄和锁标记位。
- 当一个对象已经计算过 hashcode,它就无法进入偏向锁状态。
- 当一个对象正处于偏向锁状态,且需要计算 hashcode 时,偏向锁膨胀为重量级锁。
- 线程执行同步块前,JVM 会在该线程栈帧创建存储锁记录的空间,并将对象头中的 Mark Word 复制到锁记录中。
- 锁可以升级但不能降级,以提高释放和获得锁的效率。
- 偏向锁:等待竞争才释放锁的机制。
- 检查对象头是否存储了线程 ID。
- 检查偏向锁标记。
- CAS 替换 Mark Word。
- 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。撤销偏向锁后恢复到无锁(标志位为“01”)或轻量级锁(标志位为“00”)的状态。
- 轻量级锁:其他线程通过自旋获取锁,省去阻塞的时间空间消耗,但多个线程自旋或长时间自旋开销更大,膨胀为重量级锁,轻量级锁适用于整数自增等执行非常快的场景。
原子操作实现
- 总线锁:使用处理器提供的一个
LOCK #
信号,阻塞其他处理器的请求,独占共享内存。
- 缓存锁:借助缓存一致性,优化总线锁中其它处理器无法操作其他内存地址,从而开销较大的问题。
- 操作的数据不能被缓存在处理器内部,或操作数据跨多个缓存行时,调用总线锁。
- CAS 实现原子操作的三大问题。
- ABA 问题:CAS 检查将认为其未发生变化,通过追加版本号的方式解决,JDK 的 Atomic 包提供了一个类 AtomicStampedReference。
- 循环时间长开销大:借助处理器提供的 pause 指令提升效率。
- 只能保证一个共享变量的原子操作:用锁或把多个共享变量合并为一个共享变量,JDK 提供了 AtomicReference 类来保证引用对象之间的原子性。