JDK的锁优化策略


锁的优化策略

  • JDK6对synchronized做了很多优化,引入了自适应自旋、锁消除、锁粗化、偏向锁和轻量级锁等优化策略,提高锁的效率;
  • 锁一共有4个状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁,状态会随竞争情况升级。
  • 锁可以升级但不能降级,这种只能升级不能降级的锁策略是为了提高锁获得和释放的效率。

自旋锁

  • 线程同步对性能最大的影响是阻塞,挂起和恢复线程的操作都需要从用户态转入内核态完成。
  • 许多应用上共享数据的锁定只会持续很短的时间,为了这段时间去挂起和恢复线程并不值得。
  • 如果机器有多个处理器核心,我们可以让后面请求锁的线程稍等一会, 但不放弃处理器的执行时间,看看持有锁的线程是否很快会释放锁。
  • 为了让线程等待只需让线程执行一个循环,这项技术就是自旋锁。

自旋锁在JDK4就已引入,默认关闭,在JDK6中改为默认开启。自旋不能代替阻塞,虽然避免了线程
切换开销,但要占用处理器时间。

  • 如果锁被占用的时间很短,自旋的效果就会非常好,反之只会白白消耗处理器资源。
  • 如果自旋超过了限定的次数仍然没有成功获得锁,就应挂起线程,自旋默认限定次数是10

自适应自旋

  • JDK6对自旋锁进行了优化,自旋时间不再固定,而是由前一次的自旋时间及锁拥有者的状态决定。
  • 如果在同一个锁上,自旋刚刚成功获得过锁且持有锁的线程正在运行,虚拟机会认为这次自旋也很可能成功,进而允许自旋持续更久。
  • 如果自旋很少成功,以后获取锁时将可能直接省略掉自旋,避免浪费处理器资源。有了自适应自旋,随着程序运行时间的增长,虚拟机对程序锁的状况预测就会越来越精准。

锁消除

  • 锁消除指即时编译器对检测到不可能存在共享数据竞争的锁进行消除。
  • 主要判定依据来源于逃逸分析,如果判断一段代码中堆上的所有数据都只被一个线程访问,就可以当作栈上的数据对待,认为它们是线程私有的而无须同步。

    锁粗化

  • 一般来说需要将同步块的作用范围限制得尽量小,只在共享数据的实际作用域中进行同步,这是为了使等待锁的线程尽快拿到锁。
  • 但如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体之外的,即使没有线程竞争也会导致不必要的性能消耗。
  • 因此如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把同步的范围扩展到整个操作序列的外部。

偏向锁

  • 偏向锁是为了在没有竞争的情况下减少锁开销,锁会偏向于第一个获得它的线程,如果在执行过程中锁一直没有被其他线程获取,则持有偏向锁的线程将不需要进行同步。
  • 当锁对象第一次被线程获取时,虚拟机会将对象头中的偏向模式设为1,同时使用CAS把获取到锁的线
    程ID记录在对象的Mark Word中。
  • 如果CAS成功,持有偏向锁的线程以后每次进入锁相关的同步块都不再进行任何同步操作。
  • 一旦有其他线程尝试获取锁,偏向模式立即结束,根据锁对象是否处于锁定状态决定是否撤销偏向,后续同步按照轻量级锁那样执行。

轻量级锁

轻量级锁是为了在没有竞争的前提下减少重量级锁使用操作系统互斥量产生的性能消耗。

在代码即将进入同步块时,如果同步对象没有被锁定,虚拟机将在当前线程的栈帧中建立一个锁记录空
间,存储锁对象目前Mark Word的拷贝。然后虚拟机使用CAS尝试把对象的Mark Word更新为指向
锁记录的指针,如果更新成功即代表该线程拥有了锁,锁标志位将转变为00,表示处于轻量级锁定状
念。

如果更新失败就意味着至少存在一条线程与当前线程竞争。虚拟机检查对象的Mark Word是否指向当
前线程的栈帧,如果是则说明当前线程已经拥有了锁,直接进入同步块继续执行,否则说明锁对象已经
被其他线程抢占。如果出现两条以上线程争用同一个锁,轻量级锁就不再有效,将膨胀为重量级锁,锁
标志状态变为10,此时Mark Word存储的就是指向重量级锁的指针,后面等待锁的线程也必须阻塞。

解锁同样通过CAS进行,如果对象Mark Word仍然指向线程的锁记录,就用CAS把对象当前的Mark
Word和线程复制的Mark Word替换回来。假如替换成功同步过程就顺利完成了,如果失败则说明有
其他线程尝试过获取该锁,就要在释放锁的同时唤醒被挂起的线程。

偏向锁、轻量级锁和重量级锁之间的区别?

  • 偏向锁的优点是加解锁不需要额外消耗,和执行非同步方法比仅存在纳秒级差距,缺点是如果存在锁竞争会带来额外锁撤销的消耗,适用只有一个线程访问同步代码块的场景。
  • 轻量级锁的优点是竞争线程不阻塞,程序响应速度快,缺点是如果线程始终得不到锁会自旋消耗CPU,适用追求响应时间、同步代码块执行快的场景。
  • 重量级锁的优点是线程竞争不使用自旋不消耗CPU,缺点是线程会阻塞,响应时间慢,适应追求吞吐
    量、同步代码块执行慢的场景。

Author: stream
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source stream !
  TOC