锁优化

1、自旋锁

  • 出现原因

    • 线程挂起和恢复操作影响同步性能

    • 共享数据的锁定状态只会持续很短的一段时间,为了这段时间挂起和恢复线程并不值得

  • 思想

    • 如果物理机器可以让两个或以上的线程同时并行执行,可以让后面请求锁的那个线程稍等一会,但不放弃处理器的执行时间,看看持有锁的线程是否很块就会释放锁

    • 为了让线程等待,我们只需让线程执行一个忙循环

  • JDK6之后默认开启自旋锁

  • 自旋时间

    • 默认值为10次,可以使用参数-XX:PreBlockSpin设置

    • 如果锁被占用的时间很短,自选等待的效果就会非常好

    • 如果锁被占用的时间很长,自旋只会白白消耗处理器资源

    • 如果自旋超过了限定的次数仍然美亚由成功获得锁,就应当使用传统的方式去挂起线程

  • 自适应自旋

    • 自旋时间由前一次在同一个锁上的自旋时间以及锁的持有者的状态决定

    • 如果在同一个对象锁上,自旋等待刚刚成功获得锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能成功,进而允许自旋等待持续相对更长的时间

    • 如果对于某个锁,自旋很少成功获得过锁,那么在以后要获取这个锁时就有可能直接省略掉自旋过程

2、锁消除

  • 概念

    • 虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除

  • 依据

    • 如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问,就可以把他们当作栈上数据对待,认为他们是线程私有的,不需要进行同步加锁

3、锁粗化

  • 如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,就会把锁的同步范围扩展(粗化)到整个操作序列的外部

4、轻量级锁

4.1、HotSpot虚拟机的对象头Mark Word

 锁优化

  • HotSpot虚拟机的对象

    • 一部分用于存储对象自身的运行时数据

      • 如哈希码、GC分代年龄等

      • 长度位 32bit 或 64bit

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

    • 如果是数组对象,还有一部分用于存储数组长度

4.2、轻量级锁的加锁工作过程

  • 在代码块即将进入同步块时,如果此时对象没有被锁定

  • 虚拟机首先在当前线程的栈帧中建立一个名为锁记录的空间,存储锁对象目前的对象头Mark Word的拷贝

  • 然后,虚拟机将使用CAS操作尝试把对象的Mark Word更新为指向锁记录的指针

    • 如果更新完成了,代表该线程拥有了这个对象的锁,并且对象Mark Word的标志位将变为“00”,表示此对象处于轻量级锁定状态

    • 如果更新失败,意味着至少存在一条线程与当前线程竞争获取该对象的锁,虚拟机就会检查该对象的Mark Word是否指向当前线程的栈帧

      • 如果是,表明该线程已经拥有了这个对象的锁,直接进入同步块执行

      • 如果不是,这个对象被其他线程抢占了

  • 如果出现两个以上线程争用同一个锁的情况,轻量级锁必须要膨胀为重量级锁,锁标志的状态变成“10”,Mark Word中存储的是指向重量级锁的指针,后面等待锁的进程进入阻塞状态

4.3、轻量级锁的解锁工作过程

  • 如果对象的Mark Word依然指向线程的锁记录,就用CAS操作把对象当前的Mark Word和线程中复制的那一份Mark Word替换回来

    • 如果成功替换,同步顺利完成

    • 如果替换失败,说明由其他线程尝试获取过该锁,就要在释放锁的同时,唤醒被挂起的线程

4.4、轻量级锁提升同步性能的依据

  • 对于绝大部分的锁,在整个同步周期内都是不存在竞争的

    • 如果没有竞争,轻量级锁通过CAS操作成功避免了使用互斥量的开销

    • 如果存在锁竞争,除了互斥量的本身开销外,还额外发生了CAS的开销

5、偏向锁

  • 目的

    • 在无竞争的情况下,消除掉同步操作

  • 思想

    • 加入当前虚拟机启用了偏向锁(-XX:+UseBiasedLocking),当锁对象第一次被线程获取的时候

      虚拟机就会把对象头的标志位设为“01”

      把偏向模式设置为“1”,

      再使用CAS操作把获取到这个锁的线程ID记录在对象的Mark Word中

    • 如果CAS操作成功,持有偏向锁的线程以后再次进入这个同步块时,虚拟机都可以不再进行任何同步操作

    • 一旦另外一个线程尝试获取这个锁,偏向模式立马结束

      是否撤销偏向根据锁对象目前是否处于被锁定的状态决定

      撤销后标志位恢复到未锁定或轻量级锁定的状态

  • 关于偏向锁中哈希码的问题

    • 被偏向锁锁定的对象的头信息中有23bit存储了线程ID

    • 在Java语言中,一个对象如果计算过哈希码,就应该保持不变

    • Object.hashCode()方法返回的是对象的额一致性哈希码(Identity Hash Code),这个值可以强制保持不变

      通过在对象头中存储计算结果,保证在第一次计算之后,再次调用该方法得到的哈希值不会再发生改变

    • 当一个对象已经计算过一致性哈希码后,不可进入偏向锁状态

    • 一个正在处于偏向锁状态的对象,又受到了需要计算一致性哈希码请求时,偏向状态会被撤销

  • 如果程序中大多数的锁都总是被不同的线程访问,偏向模式就会多余

上一篇:Appium app自动化测试经验分享-find_element_by_android_uiautomator ()【二】


下一篇:类加载器的双亲委派原则