【JAVA】synchronized深入研究
序言
闲来无事,记录学习历程,交个朋友。欢迎讨论碰撞,产生好的解决方案!!!👏👏👏
背景
学习一下Java的锁,巩固基础
synchronized 的三种应用方式
1. 同步实例方法
锁是当前实例对象(this)。synchronized修饰的实例方法会公用一把锁,不同对象之间不会影响,非synchronized方法也不会影响。
public synchronized void instanceMethod() {
// 同步代码块,锁是 this,即调用该方法的对象实例
// ...
}
2. 同步静态方法
锁是当前类的 Class 对象(例如 MyClass.class)。所有带synchronized的静态方法公用一把锁,不会影响实例方法,非synchronized方法也不会影响。
public static synchronized void staticMethod() {
// 同步代码块,锁是 MyClass.class
// ...
}
3. 同步代码块
锁是括号里配置的对象。
public void method() {
// 前置代码,非线程安全
synchronized (lockObject) { // lockObject 可以是任意对象
// 临界区代码,线程安全
}
// 后置代码,非线程安全
}
关键点:
- 线程在进入同步代码块/方法前必须获得锁,退出时(正常退出或异常退出)必须释放锁。
- 基于对象的内置锁(Monitor Lock)工作。
字节码分析
package com.gyg.codeing.demo2;
import java.util.concurrent.CountDownLatch;
public class CodeDemo {
private int count = 0;
public void add(int num) {
synchronized (this) {
count += num;
}
}
public synchronized void add2(int num) {
count += num;
}
public static void main(String[] args) throws InterruptedException {
CodeDemo codeDemo = new CodeDemo();
CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
codeDemo.add(1);
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println(codeDemo.count);
}
}


synchronized是由monitorenter和 monitorexit一对指令来保障的原子性的,add2因为开启了逃逸分析,导致锁被优化调了。
monitorenter和monitorexit的作用
synchonized 的语义是通过 JVM 中的 monitorenter 和 monitorexit 指令以及对象的 Monitor(监视器锁) 来实现的。
monitorenter // 尝试获取对象的 Monitor
... // 临界区代码
monitorexit // 释放 Monitor
Java对象头结构详解
对象头整体结构
| 组成部分 | 大小 | 说明 |
|---|---|---|
| Mark Word | 64 bits | 存储对象运行时数据:哈希码、GC年龄、锁状态等 |
| Klass Pointer | 32 bits | 指向类元数据的指针(开启指针压缩) |
| Array Length | 32 bits | 仅数组对象存在,存储数组长度 |
Mark Word详细结构(64位)
通用结构
| 位范围 | 字段名 | 大小 | 描述 |
|---|---|---|---|
| 63-25 | unused | 25 bits | 未使用空间 |
| 24-22 | identity_hash | 31 bits | 对象标识哈希码 |
| 21-18 | age | 4 bits | 对象分代年龄(最大15) |
| 17 | biased_lock | 1 bit | 偏向锁标识位 |
| 16-15 | lock | 2 bits | 锁状态标志位 |
| - | lock_state | - | 锁状态描述 |
不同锁状态下的Mark Word结构
无锁状态 (Normal, lock=01)
| 位范围 | 字段 | 大小 | 值 | 说明 |
|---|---|---|---|---|
| 63-39 | unused | 25 bits | - | 未使用 |
| 38-8 | identity_hash | 31 bits | - | 对象哈希码 |
| 7-4 | age | 4 bits | - | GC年龄 |
| 3 | biased_lock | 1 bit | 0 | 非偏向锁 |
| 2-1 | lock | 2 bits | 01 | 无锁状态 |
| - | state | - | Normal | 无锁 |
偏向锁状态 (Biased, lock=01)
| 位范围 | 字段 | 大小 | 值 | 说明 |
|---|---|---|---|---|
| 63-10 | thread | epoch | 54 bits | - | 线程ID + 时间戳 |
| 9-6 | age | 4 bits | - | GC年龄 |
| 5 | biased_lock | 1 bit | 1 | 偏向锁 |
| 4-3 | lock | 2 bits | 01 | 偏向锁状态 |
| - | state | - | Biased | 偏向锁 |
轻量级锁状态 (Lightweight, lock=00)
| 位范围 | 字段 | 大小 | 值 | 说明 |
|---|---|---|---|---|
| 63-2 | pointer_to_lock_record | 62 bits | - | 指向栈中锁记录指针 |
| 1-0 | lock | 2 bits | 00 | 轻量级锁 |
| - | state | - | Lightweight | 轻量级锁 |
重量级锁状态 (Heavyweight, lock=10)
| 位范围 | 字段 | 大小 | 值 | 说明 |
|---|---|---|---|---|
| 63-2 | pointer_to_monitor | 62 bits | - | 指向Monitor指针 |
| 1-0 | lock | 2 bits | 10 | 重量级锁 |
| - | state | - | Heavyweight | 重量级锁 |
GC标记状态 (Marked, lock=11)
| 位范围 | 字段 | 大小 | 值 | 说明 |
|---|---|---|---|---|
| 63-2 | GC information | 62 bits | - | GC相关信息 |
| 1-0 | lock | 2 bits | 11 | GC标记 |
| - | state | - | Marked | GC标记状态 |
锁状态转换表
| 当前状态 | 触发条件 | 下一状态 |
|---|---|---|
| 无锁 | 第一个线程访问 | 偏向锁 |
| 偏向锁 | 第二个线程竞争 | 轻量级锁 |
| 偏向锁 | 重偏向条件满足 | 偏向锁(新线程) |
| 轻量级锁 | 竞争加剧 | 重量级锁 |
| 任意状态 | GC发生 | GC标记 |
实战
通过jol可以看待,对象在JVM的一些信息。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
通过模拟枪锁的力度,来展示对象头锁的变化
package com.gyg.codeing.demo2;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import java.util.concurrent.CountDownLatch;
public class CodeDemoAsync {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 打印 JVM 详细信息
printJVM();
Thread.sleep(5000);
// 1. 查看无锁状态的对象头
noLock(new CodeDemoAsync());
Thread.sleep(5000);
// 2. 查看偏向锁状态(需要延迟一下让偏向锁生效)
biasedLock(new CodeDemoAsync());
Thread.sleep(5000);
// 3. 查看轻量级锁状态
lightLock(new CodeDemoAsync());
Thread.sleep(5000);
// 4. 查看重量级锁状态
heavyLock(new CodeDemoAsync());
Thread.sleep(5000);
}
private static void printJVM() {
System.out.println(VM.current().details());
}
private static void noLock(CodeDemoAsync demo) {
System.out.println("=== 无锁状态 ===");
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
private static void biasedLock(CodeDemoAsync demo) {
synchronized (demo) {
System.out.println("=== 偏向锁状态 ===");
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
}
private static void lightLock(CodeDemoAsync demo) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (demo) {
System.out.println("=== 轻量级锁状态 (线程1) ===");
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
});
Thread t2 = new Thread(() -> {
synchronized (demo) {
System.out.println("=== 轻量级锁状态 (线程2) ===");
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
});
t1.start();
t1.join(); // 等待t1执行完毕
t2.start();
t2.join(); // 等待t2执行完毕
}
private static void heavyLock(CodeDemoAsync demo) throws InterruptedException {
System.out.println("=== 重量级锁状态 (多线程竞争) ===");
CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
synchronized (demo) {
count += 1;
}
countDownLatch.countDown();
}).start();
}
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
countDownLatch.await();
}
}
执行分析
E:\java\jdk\jdk-21.0.8\bin\java.exe "-javaagent:E:\software\IntelliJ IDEA 2025.2.1\lib\idea_rt.jar=59249" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath E:\java\project\demo\codeing\target\classes;E:\java\maven\repository\org\openjdk\jol\jol-core\0.16\jol-core-0.16.jar com.gyg.codeing.demo2.CodeDemoAsync
# WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf
# WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.memory.Universe.getNarrowOopBase()
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
=== 无锁状态 ===
com.gyg.codeing.demo2.CodeDemoAsync object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x01003800
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== 偏向锁状态 ===
com.gyg.codeing.demo2.CodeDemoAsync object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000743e2ff5a8 (thin lock: 0x000000743e2ff5a8)
8 4 (object header: class) 0x01003800
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== 轻量级锁状态 (线程1) ===
com.gyg.codeing.demo2.CodeDemoAsync object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000743f2feeb8 (thin lock: 0x000000743f2feeb8)
8 4 (object header: class) 0x01003800
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== 轻量级锁状态 (线程2) ===
com.gyg.codeing.demo2.CodeDemoAsync object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000743f4ff128 (thin lock: 0x000000743f4ff128)
8 4 (object header: class) 0x01003800
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== 重量级锁状态 (多线程竞争) ===
com.gyg.codeing.demo2.CodeDemoAsync object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00000229ce8bb682 (fat lock: 0x00000229ce8bb682)
8 4 (object header: class) 0x01003800
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
关键观察点
- 锁状态位: 查看最后2-3位,标识锁状态
01: 无锁/偏向锁00: 轻量级锁10: 重量级锁
- 偏向锁位: 查看倒数第三位,标识是否偏向
- 线程ID: 在偏向锁状态下,可以看到持有锁的线程ID
观察结果
无锁,最后是 001
(object header: mark) 0x0000000000000001 (non-biasable; age: 0)
偏向锁
因为jdk21已经移除了,直接用轻量级锁了
轻量级锁,最后是 000
(object header: mark) 0x000000743f4ff128 (thin lock: 0x000000743f4ff128)
二进制:0000 0000 0000 0000 0000 0000 0111 0100 0011 1111 0100 1111 1111 0001 0010 1000
重量级锁,最后是 010
0x00000229ce8bb682 (fat lock: 0x00000229ce8bb682)
二进制:0000 0000 0000 0000 0000 0010 0010 1001 1100 1110 1000 1011 1011 0110 1000 0010
底层分析
下面我们分步骤详细说明:
- JVM字节码指令:当Java代码中使用了synchronized关键字时,编译后的字节码中会出现monitorenter和monitorexit指令。
- 解释执行:在解释执行时,JVM会解释这些指令。例如,在HotSpot VM中,有一个解释器,它针对每个字节码指令都有一个对应的处理代码。当遇到monitorenter时,会调用相应的例程来获取锁。
- JIT编译:当方法被JIT编译时,这些字节码指令会被编译成本地机器代码。在编译过程中,monitorenter和monitorexit会被替换成更底层的锁操作。
- 锁的实现:在底层,锁的实现通常依赖于操作系统的同步机制(如Linux下的futex)和CPU提供的原子操作(如CAS指令)。
具体来说,在HotSpot VM中,锁的实现有以下几种情况:
- 偏向锁:在无竞争的情况下,整个同步周期内只有一个线程使用锁,那么会使用偏向锁,避免多次的CAS操作。
- 轻量级锁:当有多个线程交替使用锁,但不存在同时竞争时,会使用轻量级锁,通过CAS操作来尝试获取锁。
- 重量级锁:当有多个线程同时竞争锁时,锁会升级为重量级锁,此时会使用操作系统的互斥量(mutex)来实现,导致线程阻塞和唤醒。
在重量级锁的情况下,会使用到操作系统的同步原语,例如在Linux中,会使用pthread_mutex_lock和pthread_mutex_unlock。这些操作最终会通过系统调用进入内核,由内核来调度线程。
- CPU的参与:CPU提供了原子指令,如CAS(Compare-And-Swap),用于实现轻量级锁和偏向锁。在x86架构中,CAS指令是
cmpxchg,它能够原子地比较并交换某个位置的值。如果多个线程同时执行CAS,只有一个会成功,其他的会失败。失败的线程会通过自旋(循环重试)或者进入阻塞状态(重量级锁)来等待。 - 内存屏障:为了保证可见性和有序性,锁操作还会插入内存屏障(memory barrier)。例如,在monitorenter和monitorexit对应的机器指令中,会加入适当的内存屏障,确保临界区内的读写操作不会重排序到临界区之外,并且保证锁释放时,临界区内的修改对下一个获取锁的线程可见。
- 具体步骤(以重量级锁为例):
- 当线程执行monitorenter时,如果锁对象处于未锁定状态,JVM会先尝试使用CAS操作将锁对象的标记字段设置为指向当前线程的锁记录。如果成功,则获取锁。
- 如果CAS失败,表示有竞争,JVM会膨胀为重量级锁。重量级锁会使用操作系统的互斥量,将线程阻塞,并放入等待队列。
- 当锁持有线程执行monitorexit时,会唤醒等待队列中的线程,被唤醒的线程会重新尝试获取锁。
- 与操作系统的交互:在重量级锁中,线程的阻塞和唤醒是通过操作系统内核的调度来完成的。这涉及到用户态和内核态的切换,成本较高。
总结:monitorenter和monitorexit指令在CPU层面的合作,是通过JVM将它们翻译成包含原子指令(如CAS)和操作系统同步原语(如互斥量)的代码来实现的。同时,为了内存可见性和顺序性,还会使用内存屏障。
注意:不同的JVM实现和不同的硬件平台,其具体实现细节可能会有所不同。上述描述主要针对HotSpot VM和常见的x86架构。
尾声
如果解决有解决你的问题,点点赞,求个关注👍👍👍
天生我材必有用,千金散尽还复来🤞🤞🤞,加油👏👏👏
更多推荐

所有评论(0)