本文目录导读:

我将为您详细介绍偏向锁的概念、原理和实战案例。
偏向锁概述
什么是偏向锁?
偏向锁是Java synchronized关键字的一种优化机制,核心思想是:当一个线程第一次获取锁时,会在对象头和栈帧的锁记录中记录该线程ID,后续该线程再次进入同步块时,无需再进行CAS操作,直接进入。
适用场景
- 只有一个线程访问同步块
- 线程交替执行但竞争不激烈
实战案例
案例1:基础偏向锁演示
public class BiasedLockBasic {
// 创建对象作为锁
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// JVM默认启动4秒后开启偏向锁,这里手动延迟
Thread.sleep(5000);
Runnable task = () -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName()
+ " 获取偏向锁: " + LockHelper.getLockState(lock));
}
};
// 同一个线程多次获取锁
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
synchronized (lock) {
System.out.println("第" + (i+1) + "次获取:"
+ LockHelper.getLockState(lock));
}
}
}, "单线程");
thread.start();
Thread.sleep(1000);
// 锁状态查看
System.out.println("最终锁状态: " + LockHelper.getLockState(lock));
}
}
// 锁状态查看工具类
class LockHelper {
public static String getLockState(Object obj) {
// 使用JOL工具获取对象头信息
return org.openjdk.jol.info.ClassLayout.parseInstance(obj)
.toPrintable();
}
}
案例2:偏向锁升级演示
public class BiasedLockUpgrade {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000); // 等待偏向锁启动
// 阶段1:单独一个线程获取锁(偏向锁)
System.out.println("=== 阶段1:单线程获取锁 ===");
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获取锁");
System.out.println("锁对象信息: " +
org.openjdk.jol.info.ClassLayout.parseInstance(lock).toPrintable());
}
// 线程1结束,但偏向锁仍然存在
System.out.println("线程1执行完毕,锁状态: ");
System.out.println(org.openjdk.jol.info.ClassLayout.parseInstance(lock).toPrintable());
}, "线程1");
thread1.start();
thread1.join();
Thread.sleep(1000);
// 阶段2:另一个线程尝试获取锁(锁升级为轻量级锁)
System.out.println("\n=== 阶段2:第二个线程获取锁 ===");
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程2获取锁");
System.out.println("锁对象信息: " +
org.openjdk.jol.info.ClassLayout.parseInstance(lock).toPrintable());
}
}, "线程2");
thread2.start();
thread2.join();
// 阶段3:多个线程竞争(升级为重量级锁)
System.out.println("\n=== 阶段3:多线程竞争 ===");
// 制造竞争
Thread[] threads = new Thread[3];
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
threads[i] = new Thread(() -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " 获取锁");
try {
Thread.sleep(100); // 保持持有锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
latch.countDown();
}, "竞争线程" + i);
threads[i].start();
}
latch.await();
System.out.println("最终锁状态: ");
System.out.println(org.openjdk.jol.info.ClassLayout.parseInstance(lock).toPrintable());
}
}
案例3:偏向锁批量撤销
public class BiasedLockBatchRevocation {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
// 创建多个锁对象
List<Object> locks = new ArrayList<>();
for (int i = 0; i < 30; i++) {
locks.add(new Object());
}
// 第一个线程获取所有锁(偏向锁)
System.out.println("=== 第一个线程获取所有锁 ===");
Thread thread1 = new Thread(() -> {
for (Object lock : locks) {
synchronized (lock) {
// 不做任何操作
}
}
}, "偏向线程");
thread1.start();
thread1.join();
// 第二个线程获取所有锁(触发批量撤销)
System.out.println("=== 第二个线程获取所有锁 ===");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < locks.size(); i++) {
Object lock = locks.get(i);
synchronized (lock) {
System.out.println("锁 " + i + " 状态: " +
org.openjdk.jol.info.ClassLayout.parseInstance(lock).toPrintable().split("\n")[2]);
}
}
}, "撤销线程");
thread2.start();
thread2.join();
// 查看批量撤销后新对象的状态
System.out.println("=== 批量撤销后新创建的对象 ===");
Object newLock = new Object();
System.out.println("新对象锁状态: " +
org.openjdk.jol.info.ClassLayout.parseInstance(newLock).toPrintable());
}
}
案例4:哈希码与偏向锁
public class BiasedLockHashCode {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
Object lock = new Object();
System.out.println("初始状态(无偏向锁): ");
System.out.println(org.openjdk.jol.info.ClassLayout.parseInstance(lock).toPrintable());
// 获取哈希码会撤销偏向锁
System.out.println("调用hashCode后: ");
int hash = lock.hashCode();
System.out.println("哈希值: " + hash);
System.out.println(org.openjdk.jol.info.ClassLayout.parseInstance(lock).toPrintable());
// 偏向锁与hashCode不能共存
Object lock2 = new Object();
Thread thread = new Thread(() -> {
synchronized (lock2) {
System.out.println("获取偏向锁: ");
System.out.println(org.openjdk.jol.info.ClassLayout.parseInstance(lock2).toPrintable());
// 在持有偏向锁时调用hashCode
System.out.println("偏向锁时调用hashCode: ");
System.out.println("hash值: " + lock2.hashCode());
System.out.println(org.openjdk.jol.info.ClassLayout.parseInstance(lock2).toPrintable());
}
});
thread.start();
thread.join();
}
}
运行配置
需要添加JVM参数和依赖:
<!-- Maven依赖 -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
# JVM运行参数 # 开启偏向锁(JDK8默认开启,但延迟4秒) -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 # 或者完全关闭偏向锁 -XX:-UseBiasedLocking # 设置批量撤销阈值 -XX:BiasedLockingBulkRevokeThreshold=20 -XX:BiasedLockingBulkRebiasThreshold=40
性能测试对比
public class BiasedLockPerformance {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
int threadCount = 1; // 单线程场景
int iterations = 1_000_000;
// 测试偏向锁
long start = System.currentTimeMillis();
runSingleThread(iterations);
long biasedLockTime = System.currentTimeMillis() - start;
System.out.println("偏向锁耗时: " + biasedLockTime + "ms");
}
private static void runSingleThread(int iterations) {
Object lock = new Object();
for (int i = 0; i < iterations; i++) {
synchronized (lock) {
// 模拟业务操作
int x = i + 1;
}
}
}
}
关键要点总结
偏向锁的优势
- 减少CAS操作:同一线程重复获取锁时无需CAS
- 提高单线程性能:和未加锁性能基本一致
- 适合读多写少:多个线程读取但只有一个写入的场景
偏向锁的代价
- 批量撤销开销:当锁竞争激烈时会频繁撤销
- 暂停所有线程:批量撤销时需要STW
- 内存占用:每个对象头都需要存储偏向线程ID
最佳实践建议
- JDK8:默认开启偏向锁,但有4秒延迟
- JDK15:默认禁用偏向锁
- 高竞争场景:考虑使用 -XX:-UseBiasedLocking 关闭
- 单线程场景:可以设置偏向锁延迟为0
常见问题
// 问题1:偏向锁被另一个线程获取
public class BiasedLockQuestion {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
Object lock = new Object();
// 线程A获取偏向锁
Thread a = new Thread(() -> {
synchronized (lock) {
System.out.println("线程A持有锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {}
}
}, "A");
a.start();
a.join();
// 线程B尝试获取(触发撤销)
Thread b = new Thread(() -> {
synchronized (lock) {
System.out.println("线程B获取锁(偏向锁撤销)");
}
}, "B");
b.start();
b.join();
// 线程C再次获取(锁已升级为轻量级)
Thread c = new Thread(() -> {
synchronized (lock) {
System.out.println("线程C获取锁(已升级)");
}
}, "C");
c.start();
c.join();
}
}
希望这些案例能帮助您深入理解偏向锁的工作机制和实际应用场景!