JDK1.6之后的锁优化

Posted by Gatsby on 2021-11-29

锁优化

JDK5-6的重要升级 适应性自旋 锁消除 锁粗化 轻量级锁 偏向锁 更高效地解决竞争问题

自旋锁与自适应自旋

  • 互斥同步最大的问题就是上下文切换

  • 共享数据锁定只会持续很短的时间 为了这段时间挂机或恢复线程很不值得

  • 如果不止一个核心,不如让后面的线程等一会 不放弃处理器的执行时间

  • 为了等待 所以我们让线程执行一个忙循环(自旋) while循环

  • 自旋时间默认 10 如果自旋时间太长 也会白白消耗处理资源 浪费性能

  • 自适应自旋:自旋时间不是固定的 自旋时间由上一个自旋时间和拥有者决定 如果很少成功获得锁,就会省略自旋过程 如果经常成功 就会试图更长地自旋

    锁消除

  • 就是在即时编译器运行时,对不可能存在竞争的锁进行消除

  • 如果数据不会逃逸出去被其他线程访问 就可以当成栈上数据 经典栈上分配 就不用加锁

  • image.png

  • 字符串加法JDK5之前 会转为StringBuffer JDK6之后是StringBuilder

  • image.png

  • sb对象会在append的时候加锁 但是sb不会逃逸出去 就需要锁消除

    锁粗化

  • 如果一系列连续操作都需要反复加锁和解锁,即使没有线程竞争,频繁的互斥同步操作也会导致不必要的性能损耗

  • 锁粗化:对于零碎的锁 如果检测到,就要扩大锁的范围 只要加锁一次就好

    轻量级锁

  • JDK轻量级锁,相对sync重量级锁来说非常轻

  • 轻:不是代替重 只是在没有多线程竞争下 减少重量级锁的性能损耗

  • 轻量级锁 要考虑偏向锁的原理和运作过程

  • **要对HotSpot内存布局有了解 **

  • HotSpot对象头 两部分 第一部分(运行时数据) 哈希码 GC年龄等 在JVM占用32或64字节 称为“Mark Word”

  • 另一部分 存储指向方法区对象类型数据的指针

  • Mark Word是一个非固定的动态数据结构

  • image.png

  • 轻量级锁工作流程 代码快进入同步块的时候,如果同步对象未被锁定(锁标志01),JVM在当前线程栈中创建一个 **锁记录(Lock Record) **的空间 存储 Mark Word

  • image.png

  • JVM用CAS操作 尝试把对象的Mark Word更新为指向Lock Record指针

  • 如果成功 就代表拥有了这个对象的锁 修改锁标志位 00 表示轻量级锁定状态

  • 如果更新失败了,就说明有线程竞争 先判断Mark Word是否指向当前栈帧

  • 是说明已经有锁直接进入同步块就好 没有就说明抢占 要用重量级锁

  • 锁标志10

  • 解锁也是用CAS操作 如果对象的Mark Word仍指向线程的锁记录 就把复制的换回来就好

  • 如果有竞争在释放锁的同时,唤醒被挂起的线程

  • CAS依据:**对于绝大部分锁,在同步周期内不存在竞争 **这一经验法则

  • 优势:成功避免频繁使用互斥量开销

  • 劣势:如果确实存在锁竞争 除了锁开销还增加了CAS开销

    偏向锁

  • 目的:消除数据在无竞争下的同步原语

  • CAS:消去同步使用的互斥量

  • 偏向锁:就是消去整个同步 CAS都不用了

  • 偏:锁会偏向于第一个获得它的线程 如果接下来锁一直没被别人获取,就永远不用同步

  • 锁第一次被线程获取的时候 对象头标志设置为01 偏向模式 1

  • CAS操作把获取到锁的线程ID记录在Mark Word

  • CAS成功 每次持有偏向锁的线程进入相关同步块 可以不进行同步操作

  • 一旦出现另外一个线程尝试获取锁,偏向模式马上结束 按照轻量级锁去执行

  • image.png

  • 哈希码的位置被占问题:如果一个对象计算过哈希码之后,就不会进入偏向状态 如果同步需要计算哈希码,会撤销偏向状态改为重量级锁

  • 缺点:对经常有线程竞争的程序效果不好 反而增加开销

    参考资料

  • 《深入理解Java虚拟机:JVM高级特性与最佳实践》第三版 -周志明

  • 《Java并发编程的艺术》 -方腾飞