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。
        • CAS:比较并交换,乐观锁的典型实现。
      • 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。撤销偏向锁后恢复到无锁(标志位为“01”)或轻量级锁(标志位为“00”)的状态。
  • 轻量级锁:其他线程通过自旋获取锁,省去阻塞的时间空间消耗,但多个线程自旋或长时间自旋开销更大,膨胀为重量级锁,轻量级锁适用于整数自增等执行非常快的场景。

原子操作实现

  • 总线锁:使用处理器提供的一个 LOCK # 信号,阻塞其他处理器的请求,独占共享内存。
  • 缓存锁:借助缓存一致性,优化总线锁中其它处理器无法操作其他内存地址,从而开销较大的问题。
    • 操作的数据不能被缓存在处理器内部,或操作数据跨多个缓存行时,调用总线锁。
  • CAS 实现原子操作的三大问题。
    • ABA 问题:CAS 检查将认为其未发生变化,通过追加版本号的方式解决,JDK 的 Atomic 包提供了一个类 AtomicStampedReference。
    • 循环时间长开销大:借助处理器提供的 pause 指令提升效率。
    • 只能保证一个共享变量的原子操作:用锁或把多个共享变量合并为一个共享变量,JDK 提供了 AtomicReference 类来保证引用对象之间的原子性。