在 JDK 1.5 之前,Java 并发编程中的 synchronized
关键字是解决多线程同步问题的主要手段。通过 synchronized
关键字,Java 提供了三种同步方式:
- 同步方法:锁上当前实例对象。
- 同步静态方法:锁上当前类的
Class
对象。 - 同步块:锁上代码块里面配置的对象。
例如,下面的代码展示了如何使用同步块:
public void test() {
synchronized (object) {
i++;
}
}
通过 javap -v
编译后的指令可以看到,monitorenter
指令会插入到同步代码块的开始位置,而 monitorexit
指令会插入到方法结束和异常的位置(实际隐藏了 try-finally
)。每个对象都有一个 monitor
与之关联,当一个线程执行到 monitorenter
指令时,就会获得对象所对应 monitor
的所有权,也就获得到了对象的锁。
1 对象监视器 monitor
在 Java 中,monitor
可以被看作是一种守门人或保安,它确保同一时刻只有一个线程可以访问受保护的代码段。monitor
的工作方式如下:
- 进入房间:当一个线程想要进入受保护的代码区域(房间)时,它必须得到
monitor
的允许。如果房间里没有其他线程,monitor
会让它进入并关闭门。 - 等待其他线程:如果房间里已经有一个线程,其他线程就必须等待。
monitor
会让其他线程排队等候,直到房间里的线程完成工作离开房间。 - 离开房间:当线程完成它的工作并离开受保护的代码区域时,
monitor
会重新打开门,并让等待队列中的下一个线程进入。 - 协调线程:
monitor
还可以通过一些特殊的机制(例如wait
和notify
方法)来协调线程之间的合作。
2 重量级锁
当另一个线程执行到同步块时,由于它没有对应 monitor
的所有权,就会被阻塞,此时控制权只能交给操作系统,从用户模式切换到内核模式,由操作系统来负责线程间的调度和线程的状态变更。这种频繁的上下文切换会引起很大的开销,因此被称为重量级锁。
3 轻量级锁
如果 CPU 通过 CAS(Compare-And-Swap)就能处理好加锁/释放锁,这样就不会有上下文的切换。但是当竞争很激烈时,CAS 尝试再多也是浪费 CPU,权衡一下,不如升级成重量级锁,阻塞线程排队竞争,也就有了轻量级锁升级成重量级锁的过程。
4 偏向锁
HotSpot 的作者研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。同一个线程反复获取锁,如果还按照 CAS 的方式获取锁,也是有一定代价的。如何让这个代价更小一些呢?
偏向锁实际上就是「锁对象」潜意识「偏向」同一个线程来访问,让锁对象记住这个线程 ID,当线程再次获取锁时,亮出身份,如果是同一个 ID 直接获取锁就好了,是一种 load-and-test
的过程,相较 CAS 又轻量级了一些。
可是多线程环境,也不可能只有同一个线程一直获取这个锁,其他线程也是要干活的,如果出现多个线程竞争的情况,就会有偏向锁升级的过程。
4.1 偏向锁的升级过程
偏向锁可以绕过轻量级锁,直接升级到重量级锁吗?答案是可以的。偏向锁的升级过程如下:
- 无竞争的情况下,只有一个线程进入临界区,采用偏向锁。
- 多个线程可以交替进入临界区,采用轻量级锁。
- 多线程同时进入临界区,交给操作系统互斥量来处理,升级为重量级锁。
4.2 Java 对象头
在 Java 中,对象头(Object Header)是对象在内存中的布局的一部分,它包含了对象的元数据信息。对象头中的 MarkWord
是保存锁状态的关键部分,它记录了对象的锁状态、哈希码、GC 信息等。通过对象头,Java 可以高效地管理对象的锁状态,从而实现线程同步。
4.2.1 对象头的结构
Java 对象头最多由三部分构成:
- MarkWord:保存对象的锁状态、哈希码、GC 信息等。
- ClassMetadata Address:指向对象的类元数据的指针。
- Array Length(如果对象是数组才会有这部分):数组的长度。
其中,MarkWord
是保存锁状态的关键部分。对象的锁状态可以从无锁状态升级到偏向锁、轻量级锁,最后到重量级锁。
4.2.2 偏向锁的延迟开启
在 JDK 1.6 之后,默认是开启偏向锁的,但开启有延迟,大约 4 秒。这是因为在 JVM 内部有很多地方使用了 synchronized
,如果直接开启偏向锁,产生竞争时就需要锁升级,这会带来额外的性能损耗。因此,JVM 采用了延迟策略。
你可以通过参数 -XX:BiasedLockingStartupDelay=0
将延迟改为 0,但不建议这么做。
4.2.3 使用 JOL 查看对象头
为了更好地理解对象头的变化,我们可以使用 JOL
(Java Object Layout)工具来查看对象的内存布局。
4.2.3.1 引入 JOL 依赖
首先,在 pom.xml
中引入 JOL 依赖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
4.2.3.2 场景 1:默认延迟下的对象头变化
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
@Slf4j
public class SynchronizedBlockTest {
public static void main(String[] args) {
Object o = new Object();
log.info("未进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
log.info("进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
}
}
}
输出结果:
15:55:47.551 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - 1. 未进入同步块,MarkWord 为:
15:55:49.828 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:55:49.829 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - 2. 进入同步块,MarkWord 为:
15:55:49.829 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000000318f7c8 (thin lock: 0x000000000318f7c8)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
4.2.3.3 场景 2:延迟 5 秒后的对象头变化
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
@Slf4j
public class SynchronizedBlockTest {
public static void main(String[] args) throws InterruptedException {
// 睡眠 5s
Thread.sleep(5000);
Object o = new Object();
log.info("未进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
log.info("进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
}
}
}
输出结果:
15:56:41.697 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - 1. 未进入同步块,MarkWord 为:
15:56:43.876 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:56:43.876 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - 2. 进入同步块,MarkWord 为:
15:56:43.877 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000002b2f0b8 (thin lock: 0x0000000002b2f0b8)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
4.2.3.4 场景 3:多线程竞争下的对象头变化
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
@Slf4j
public class SynchronizedBlockTest {
public static void main(String[] args) throws InterruptedException {
// 睡眠 5s
Thread.sleep(5000);
Object o = new Object();
log.info("未进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
log.info("进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
}
Thread t2 = new Thread(() -> {
synchronized (o) {
log.info("新线程获取锁,MarkWord为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
}
});
t2.start();
t2.join();
log.info("主线程再次查看锁对象,MarkWord为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
log.info("主线程再次进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
}
}
}
输出结果:
15:57:29.803 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - 1. 未进入同步块,MarkWord 为:
15:57:31.926 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:57:31.926 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - 2. 进入同步块,MarkWord 为:
15:57:31.927 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000002e8f7a0 (thin lock: 0x0000000002e8f7a0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:57:31.928 [Thread-0] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - 3. 新线程获取锁,MarkWord为:
15:57:31.928 [Thread-0] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00000000231bf4c0 (thin lock: 0x00000000231bf4c0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:57:31.928 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - 4. 主线程再次查看锁对象,MarkWord为:
15:57:31.929 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:57:31.929 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - 5. 主线程再次进入同步块,MarkWord 为:
15:57:31.929 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.synchronizedBlockTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000002e8f7a0 (thin lock: 0x0000000002e8f7a0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
4.3 偏向锁的撤销与批量重偏向
在 Java 中,偏向锁是一种优化机制,旨在减少无竞争情况下的锁开销。然而,当多个线程竞争同一个锁时,偏向锁可能会被撤销,从而导致性能下降。为了解决这个问题,JVM 引入了偏向锁的撤销和批量重偏向机制。
4.3.1 偏向锁的撤销
在讨论偏向锁的撤销之前,我们需要明确两个概念:
- 偏向锁撤销:当多个线程竞争同一个锁时,偏向锁不能再使用偏向模式,需要撤销偏向锁。
- 偏向锁释放:当
synchronized
方法或块结束时,锁会被释放。
何为偏向撤销?
偏向撤销是指将偏向锁的状态从偏向状态撤回到原来的状态,即将 MarkWord
的第 3 位(是否偏向撤销)的值从 1 变回 0。偏向锁的撤销只能发生在有竞争的情况下,因为如果只有一个线程获取锁,再加上「偏心」的机制,是没有理由撤销偏向的。
撤销过程
想要撤销偏向锁,还不能对持有偏向锁的线程有影响,就需要等待持有偏向锁的线程到达一个 safepoint
安全点。在这个安全点,JVM 会挂起获得偏向锁的线程。在 safepoint
,线程可能处于不同的状态:
- 线程不存活,或者活着的线程退出了同步块,直接撤销偏向。
- 活着的线程但仍在同步块之内,那就升级成轻量级锁。
4.3.2 批量重偏向(bulk rebias)
为了减少大量撤销偏向锁带来的性能损耗,JVM 引入了批量重偏向机制。批量重偏向以 class
为单位,为每个 class
维护一个偏向锁撤销计数器。当一个 class
的对象发生偏向撤销的次数达到重偏向阈值(默认 20)时,JVM 会进行批量重偏向。
BiasedLockingBulkRebiasThreshold = 20
实现方式
批量重偏向的实现方式是通过 epoch
字段。每个 class
对象会有一个对应的 epoch
字段,每个处于偏向锁状态对象的 MarkWord
中也有该字段,其初始值为创建该对象时 class
中的 epoch
的值。
每次发生批量重偏向时,就将该值加 1,同时遍历 JVM 中所有线程的栈:
- 找到该
class
所有正处于加锁状态的偏向锁对象,将其epoch
字段改为新值。 class
中不处于加锁状态的偏向锁对象,保持epoch
字段值不变。
这样下次获得锁时,发现当前对象的 epoch
值和 class
的 epoch
值不同,就会直接通过 CAS 操作将其 MarkWord
的线程 ID 改成当前线程 ID,而不会执行撤销操作。
4.3.3 批量撤销(bulk revoke)
当达到重偏向阈值后,假设该 class
计数器继续增长,当其达到批量撤销的阈值(默认 40)时,JVM 就认为该 class
的使用场景存在多线程竞争,会标记该 class
为不可偏向。之后对于该 class
的锁,直接走轻量级锁的逻辑。
BiasedLockingBulkRevokeThreshold = 40
批量撤销的过渡过程
在彻底禁用偏向锁之前,JVM 还会给一次改过自新的机会。如果在距离上次批量重偏向发生的 25 秒之内,并且累计撤销计数达到 40,就会发生批量撤销(偏向锁彻底 game over)。如果在距离上次批量重偏向发生超过 25 秒之外,就会重置在 [20, 40) 内的计数,再给次机会。
4.4 偏向锁与 HashCode
在 Java 中,对象的 hashCode
是一个重要的属性,它用于对象的唯一标识。然而,hashCode
的生成和存储与对象的锁状态(如偏向锁、轻量级锁、重量级锁)之间存在一定的关系。
4.4.1 场景一:未生成 hashCode
时的对象头
public static void main(String[] args) throws InterruptedException {
// 睡眠 5s
Thread.sleep(5000);
Object o = new Object();
log.info("未生成 hashcode,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
o.hashCode();
log.info("已生成 hashcode,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
log.info("进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
}
}
运行结果:
15:51:48.882 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - 未生成 hashcode,MarkWord 为:
15:51:51.104 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:51:51.104 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - 已生成 hashcode,MarkWord 为:
15:51:51.105 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000003d36e4cd01 (hash: 0x3d36e4cd; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:51:51.105 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - 进入同步块,MarkWord 为:
15:51:51.106 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00000000035df798 (thin lock: 0x00000000035df798)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
结论:即便初始化为可偏向状态的对象,一旦调用 Object::hashCode()
或者 System::identityHashCode(Object)
,进入同步块就会直接使用轻量级锁。
4.4.2 场景二:已偏向某一个线程,然后生成 hashCode
public static void main(String[] args) throws InterruptedException {
// 睡眠 5s
Thread.sleep(5000);
Object o = new Object();
log.info("未生成 hashcode,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
log.info("进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
}
o.hashCode();
log.info("生成 hashcode");
synchronized (o) {
log.info("同一线程再次进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
}
}
运行结果:
15:50:51.550 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - 未生成 hashcode,MarkWord 为:
15:50:53.758 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:50:53.758 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - 进入同步块,MarkWord 为:
15:50:53.759 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000000345f4d0 (thin lock: 0x000000000345f4d0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:50:53.759 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - 生成 hashcode
15:50:53.759 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - 同一线程再次进入同步块,MarkWord 为:
15:50:53.759 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000000345f4d0 (thin lock: 0x000000000345f4d0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
结论:同场景一,会直接使用轻量级锁。
4.4.3 场景三:已偏向状态,在同步块中生成 hashCode
public static void main(String[] args) throws InterruptedException {
// 睡眠 5s
Thread.sleep(5000);
Object o = new Object();
log.info("未生成 hashcode,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
log.info("进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
o.hashCode();
log.info("已偏向状态下,生成 hashcode,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
}
}
运行结果:
15:50:00.220 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - 未生成 hashcode,MarkWord 为:
15:50:02.410 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:50:02.410 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - 进入同步块,MarkWord 为:
15:50:02.410 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000002e7f288 (thin lock: 0x0000000002e7f288)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:50:02.410 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - 已偏向状态下,生成 hashcode,MarkWord 为:
15:50:02.411 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.HashCodeTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001cff50da (fat lock: 0x000000001cff50da)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
结论:如果对象处在已偏向状态,生成 hashCode
后,就会直接升级成重量级锁。
4.4.4 小结
对象的 hashCode
生成和存储与对象的锁状态之间存在一定的关系。具体来说:
- 未生成
hashCode
:对象的MarkWord
状态可以是偏向锁、轻量级锁或无锁状态。 - 已生成
hashCode
:对象的MarkWord
状态会直接进入轻量级锁状态,即使对象之前是偏向锁状态。 - 已偏向状态,生成
hashCode
:对象的MarkWord
状态会直接升级成重量级锁。
4.5 重量级锁与 Object.wait
Object
的 wait()
方法是互斥量(重量级锁)独有的,一旦调用该方法,就会升级成重量级锁。
代码示例
以下是一个示例代码,展示了在同步块中调用 wait()
方法对锁的影响:
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
@Slf4j
public class SynchronizedBlockTest {
public static void main(String[] args) throws InterruptedException {
// 睡眠 5s
Thread.sleep(5000);
Object o = new Object();
log.info("未生成 hashcode,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintab![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7933dc896d804c369d3c91c12450041c.png#pic_center)
le());
synchronized (o) {
log.info("进入同步块,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
log.info("wait 2s");
o.wait(2000);
log.info("调用 wait 后,MarkWord 为:");
log.info(ClassLayout.parseInstance(o).toPrintable());
}
}
}
运行结果
15:45:09.168 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.WaitTest - 未生成 hashcode,MarkWord 为:
15:45:11.344 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.WaitTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:45:11.344 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.WaitTest - 进入同步块,MarkWord 为:
15:45:11.345 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.WaitTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000000259f218 (thin lock: 0x000000000259f218)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15:45:11.345 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.WaitTest - wait 2s
15:45:13.355 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.WaitTest - 调用 wait 后,MarkWord 为:
15:45:13.356 [main] INFO com.yunyang.javabetter.juc.pianxiangsuo.WaitTest - java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001cb66dea (fat lock: 0x000000001cb66dea)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
4.6 再见偏向锁
由于维护成本过高,JDK 15 开始,偏向锁默认被禁用。虽然偏向锁可能在未来的某个版本中被移除,但了解其工作原理仍然有助于我们理解 Java 并发编程的底层机制。
5 总结
偏向锁是 Java 并发编程中的一种优化手段,旨在减少无竞争情况下的锁开销。然而,随着 JDK 版本的演进,偏向锁的维护成本逐渐显现,最终在 JDK 15 中被默认禁用。尽管如此,理解偏向锁的工作原理仍然有助于我们更好地掌握 Java 并发编程的核心概念。