JAVA偏向锁、轻量级锁、重量级锁介绍

312 阅读4分钟

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

1. 前言

今天我们来讲讲多线程中的锁部分中的的⽆锁/偏向锁/轻量级锁/重量级锁。为了换取性能,JVM在内置锁上做了非常多的优化,膨胀式的锁分配策略就是其一。理解偏向锁、轻量级锁、重量级锁的要解决的基本问题,几种锁的分配和膨胀过程,有助于编写并优化基于锁的并发程序。

首先,⽆锁/偏向锁/轻量级锁/重量级锁这四种锁是指锁的状态,并且是针对Synchronized。在Java 通过引入锁升级的机制来实现高效Synchronized。这四种锁的状态是通过对象监视器在对象头中的Mark Word来表明的。Mark Word这部分主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。

2. 无锁

无锁很好理解,就是代码没有上锁,此时如果并发操作就有可能会产生问题,使得程序获得不正确的结果。

3. 偏向锁

偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。偏向锁的目标是,减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗,在偏向锁中会标记owner,如果记录成功,则偏向锁获取成功*,记录锁状态为偏向锁,当有多个线程竞争锁的时候,偏向锁会升级为轻量级锁

4. 轻量级锁

轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,如果说偏向锁是只允许一个线程获得锁,那么轻量级锁就是允许多个线程获得锁,但是只允许他们顺序拿锁,不允许出现竞争,也就是拿锁失败的情况,如果完全没有实际的锁竞争,那么申请重量级锁都是浪费的。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。

5. 重量级锁

重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

6. 三种锁状态对比

优点缺点使用场景
偏向锁仅执行一次CAS操作,加锁和解锁不需要额外的消耗,和执行非同步方法时相比仅存在纳秒级的差距如果线程间存在锁竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步的场景
轻量级锁竞争的线程顺序执行,不会阻塞,提高了程序的响应速度;相比偏向锁,获取和释放锁均执行一次CAS操作如果使用得不到锁竞争的线程,会使用自旋会消耗CPU资源追求响应时间,同步块执行速度非常快
重量级锁线程竞争不使用自旋,不会消耗CPU线程阻塞,响应时间缓慢追求吞吐量,同步块执行速度较长

7.偏向锁被禁用

JEP 374: Deprecate and Disable Biased Locking

JDK 15 之前,偏向锁默认是 enabled,从 15 开始,默认就是 disabled,除非显示的通过 UseBiasedLocking 开启.

总的来说,偏向锁给 JVM 增加了巨大的复杂性,只有少数非常有经验的程序员才能理解整个过程,维护成本很高,大大阻碍了开发新特性的进程。