偏向锁 / 轻量级锁 / 重量级锁
原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11395931.html
锁的状态
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态
锁的状态是通过对象监视器在对象头中的字段来表明的。
为了提升性能,JDK1.6引入了偏向锁、轻量级锁、重量级锁概念,来减少锁竞争带来的上下文切换,而正是新增的Java对象头实现了锁升级功能。
当Java对象呗Synchronized关键字修饰成同步锁后,围绕这个锁的一系列升级操作都将和Java对象头有关。
Java对象头
在JDK1.6的JVM中,对象实例在堆内存中被分为了三个部分:对象头、实例数据、对齐填充。其中对象头由Mark Word、指向类的指针以及数组长度三部分组成。
Mark Word记录了对象和锁有关的信息。Mark Word在64位JVM中的长度是64bit,在64位JVM的存储结构如下图所示
锁升级主要依赖于Mark Word中的锁标志位和释放偏向锁标志位,Synchronized同步锁就是从偏向锁开始的,四种状态会随着竞争的情况逐渐升级到轻量级锁,最终升级到重量级锁,而且是不可逆的过程,即不可降级。
这四种状态都不是Java语言中的锁,而是JVM在使用synchronized时为了提高锁的获取与释放效率而做的优。
偏向锁
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
偏向锁主要用来优化同一个线程多次申请同一个锁的竞争,在某些情况下,大部分时间是同一个线程竞争锁资源。一旦出现其他线程竞争资源时,偏向锁就会被撤销。偏向锁的撤销需要等待全局安全点,暂停持有该锁的线程,同时检查该线程是否还在执行该方法,如果是,则升级锁,反之则被其他线程抢占。
因此在高并发场景下,当大量线程同时竞争同一个锁资源时,偏向锁就会被撤销,发生STW后,开启偏向锁会带来更大的性能开销,这时可以通过添加JVM参数关闭偏向锁来调优系统性能。
-XX:-UseBiasedLocking // 关闭偏向锁(默认打开)
或
-XX:+UseHeavyMonitors // 设置重量级锁
轻量级锁
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
轻量级锁适用于线程交替执行同步块的场景,绝大部分的锁在整个同步周期内都不存在长时间的竞争。
重量级锁
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
具体指轻量级锁CAS抢锁失败,线程将会被挂起进入阻塞状态。如果正在持有锁的线程在很短的时间内释放资源,那么进入阻塞状态的线程无疑又要申请锁资源。
JVM提供了一种自旋锁,可以通过自旋方式不断尝试获取锁,从而避免线程被挂起阻塞。这是基于大多数情况下,线程持有锁的时间都不会太长,毕竟线程被挂起阻塞可能会得不偿失。
JDK1.7开始,自旋锁默认启用,自旋次数由JVM设置决定,通常不建议设置的JVM重试次数过多,因为CAS重试操作意味着长时间地占用CPU。
自旋锁重试之后如果抢锁依然失败,同步锁就会升级至重量级锁。
在锁竞争不激烈且锁占用时间非常短的场景下,自旋锁可以提高系统性能。一旦锁竞争激烈或锁占用的时间过长,自旋锁将会导致大量的线程一直处于CAS重试状态,占用CPU资源,反而会增加系统性能开销。所以自旋锁和重量级锁的使用都要结合实际场景。
在高负载、高并发的场景下,可以通过设置JVM参数来关闭自旋锁,优化系统性能。
-XX:-UseSpinning // 参数关闭自旋锁优化 (默认打开) -XX:PreBlockSpin // 参数修改默认的自旋次数。JDK1.7 后,去掉此参数,由 jvm 控制
Reference
https://time.geekbang.org/column/article/101244