Java 偏向锁适用场景
下文笔者讲述java偏向锁简介说明,如下所示
偏向锁核心特性
偏向锁是Jav 锁优化(偏向锁→轻量级锁→重量级锁)
核心设计目标是消除无竞争场景下的锁获取开销,
其关键特性如下:
1.偏向性:
锁会偏向于第一个获取它的线程,
一旦线程获取锁后,后续该线程再次获取锁时,
无需进行 CAS 操作或阻塞等待,仅需通过「线程 ID」判断即可快速获取锁;
2.低开销:
偏向锁的获取和释放仅涉及少量的内存操作(修改锁对象头的偏向线程 ID),
几乎无性能损耗,
远低于轻量级锁的 CAS 操作和重量级锁的内核态切换;
3.无竞争前提:
偏向锁仅在「单线程重复获取同一把锁」的场景下发挥最优性能,
一旦出现多线程竞争,
偏向锁会升级为轻量级锁(或直接撤销偏向锁);
4.延迟初始化:
JDK 1.6 及以上默认开启偏向锁,
但会有一定延迟(默认 4 秒),
避免程序启动初期大量线程竞争导致偏向锁频繁撤销。
二、偏向锁核心适用场景
偏向锁的优势在于「低开销重复获取锁」,
核心适用场景集中在单线程主导、无多线程竞争,
且同一线程频繁重复获取同一把锁的业务场景,
场景1:单线程重复操作同一锁资源(核心适用场景)
这是偏向锁的最优适用场景,
即只有一个线程持续获取、释放同一把锁,
不存在任何其他线程竞争该锁。
此时偏向锁会长期偏向该线程,
避免了锁获取的额外开销,
最大化提升性能。
例:
- 单线程环境下的集合操作
(如单线程对 `Vector`、`synchronized` 修饰的自定义集合进行频繁添加、删除操作);
- 单线程执行的串行任务
(如单线程处理文件读写、数据解析,
其中包含多个同步方法/代码块,且均由同一线程调用);
- 单线程中的循环同步操作
(如循环中多次调用 synchronized 方法,处理批量数据)。
例(单线程重复获取偏向锁)
public class BiasedLockSingleThreadDemo {
// 锁对象,默认开启偏向锁
private static final Object LOCK = new Object();
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 等待偏向锁延迟初始化完成(默认4秒,此处等待5秒确保生效)
Thread.sleep(5000);
// 单线程重复获取同一把锁
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
synchronized (LOCK) {
count++; // 临界区操作,单线程执行无竞争
}
}
long end = System.currentTimeMillis();
System.out.println("执行完成,count=" + count);
System.out.println("耗时:" + (end - start) + " 毫秒");
// 偏向锁场景下耗时极短,远低于轻量级锁/重量级锁
}
}
场景2:少量线程交替执行,无实际竞争(伪多线程场景)
存在多个线程,
但线程之间是「交替执行」的,
不存在同时竞争同一把锁的情况(即同一时间只有一个线程持有锁,
后续线程获取锁时,前一个线程已释放锁)。
此时偏向锁可依次偏向不同线程,避免锁升级,保持低开销。
典型示例:
- 线程池核心线程数为 1 的场景
(所有任务串行执行,由同一个核心线程处理,锁始终偏向该核心线程);
- 线程间通过顺序执行
(如 `Thread.join()`)实现交替操作同一锁资源,无并发竞争;
- 定时任务
(如 `Timer` 单线程调度,多个定时任务串行执行,同步代码块无竞争)。
场景3:高频次、短时间的同步操作(细粒度同步)
业务场景中存在大量高频次、
执行时间极短的同步操作(如简单的变量修改、状态判断),
且这些操作主要由单个线程完成,无多线程竞争。
偏向锁的低开销特性可最大化减少同步操作对性能的影响。
典型示例:
- 单线程下的计数器更新(如接口调用次数统计、数据处理条数统计,使用 synchronized 保证线程安全,无多线程竞争);
- 单线程中的缓存更新(如本地缓存的添加、查询、删除,同步代码块执行时间短,频繁重复调用);
- 工具类中的同步方法(如单线程使用的工具类,其同步方法被频繁调用,无多线程竞争)。
场景4:长期运行的单线程服务(后台守护线程)
后台守护线程长期运行,
且在生命周期内持续获取同一把锁处理任务,
无其他线程竞争该锁。偏向锁可长期偏向该守护线程,
避免锁操作带来的性能损耗。
典型示例:
- 日志输出线程
(单线程负责日志写入文件,同步代码块保证日志顺序,无多线程竞争);
- 数据备份线程
(后台单线程定时备份数据,同步操作保证备份过程的原子性,无并发竞争);
- 监控采集线程
(单线程定时采集应用指标,同步代码块保护采集结果的完整性,无多线程竞争)。
三、偏向锁不适用场景(避免误用导致性能下降)
当场景中存在多线程竞争或不满足「单线程重复获取」条件时,
偏向锁不仅无法提升性能,还会因「偏向锁撤销/升级」带来额外开销,具体不适用场景如下:
场景1:多线程并发竞争同一锁资源(核心不适用场景)
这是偏向锁最不适用的场景,当多个线程同时竞争同一把锁时,
偏向锁会频繁发生「偏向撤销」或「锁升级」:
- 首先,偏向锁会尝试撤销偏向(修改锁对象头的线程 ID),
该过程需要暂停所有线程(SafePoint),带来额外开销;
- 若撤销失败或竞争持续,偏向锁会升级为轻量级锁,
此时偏向锁的优化失去意义,反而因前期的偏向操作增加了性能损耗。
典型示例:
- 高并发场景下的共享资源竞争
(如秒杀系统中多个线程竞争同一商品库存锁);
- 多线程同时调用同一个 synchronized 方法处理业务
(如多线程批量处理数据,同步方法存在并发竞争)。
场景2:锁被多个线程交替获取,但竞争频繁
即使线程不是同时竞争锁,但多个线程交替获取锁的频率极高
(如线程 A 释放锁后,线程 B 立即获取,随后线程 A 又再次获取),
此时偏向锁会频繁切换偏向的线程,导致大量的偏向锁撤销操作,性能反而低于直接使用轻量级锁。
典型示例:
- 线程池核心线程数大于 1,且任务均需要获取同一把锁;
- 多线程交替执行的高频同步任务(如两个线程交替修改同一共享变量,同步代码块竞争频繁)。
场景3:同步代码块执行时间过长
偏向锁的优势是「低开销」,
但如果同步代码块执行时间过长(如包含大量 IO 操作、复杂计算),
即使是单线程执行,偏向锁的性能优化效果也不明显,
反而可能因锁持有时间过长,导致后续线程竞争时锁升级的开销增加。
典型示例:
- 同步代码块中包含文件读写、网络请求等耗时 IO 操作;
- 同步方法中执行复杂的算法计算(耗时数百毫秒甚至秒级)。
场景4:大量短生命周期线程频繁创建与销毁
若系统中存在大量短生命周期线程(线程创建后快速执行任务并销毁),
且这些线程都会获取同一把锁,此时偏向锁会频繁偏向新线程,
导致大量的偏向锁撤销和重新偏向操作,带来额外性能损耗。
典型示例:
- 无界线程池处理大量短期任务,任务均需要获取同一同步锁;
- 频繁创建临时线程执行同步操作,线程执行完毕后立即销毁。
场景5:不需要同步的场景(误用偏向锁)
若业务场景本身不存在线程安全问题,无需使用 synchronized 或 Lock 锁,
此时开启偏向锁毫无意义,反而会因锁对象头的偏向标记带来微小的额外开销。
偏向锁使用注意事项
1. 开启与关闭配置:
- JDK 1.6 及以上默认开启偏向锁,可通过 JVM 参数调整:
- 开启偏向锁(默认):`-XX:+UseBiasedLocking`
- 关闭偏向锁:`-XX:-UseBiasedLocking`(适用于多线程竞争频繁的场景)
- 关闭偏向锁延迟初始化:`-XX:BiasedLockingStartupDelay=0`
(适用于程序启动后立即需要偏向锁优化的场景)
2. 偏向锁撤销的开销:
偏向锁撤销是一个重量级操作(需要暂停所有线程到 SafePoint),
因此在存在竞争的场景下,提前关闭偏向锁比让其自动升级更高效。
3. 与其他锁优化的配合:
偏向锁是锁优化的第一阶段,当竞争出现时会自动升级为轻量级锁(CAS 自旋),
若自旋失败则升级为重量级锁(阻塞),无需手动干预,但需根据业务场景选择是否开启偏向锁。
4. 锁对象的复用:
对于频繁使用的锁对象,尽量复用(避免频繁创建新的锁对象),
这样偏向锁能长期偏向同一线程,发挥最优性能;若频繁创建新锁对象,
会导致偏向锁无法复用,失去优化效果。
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。


