一、synchronized的基础使用方法(入门)
synchronized是Java内置的互斥锁,核心作用是保证多线程下同步代码的原子性、可见性和有序性。它有3种核心使用方式,本质都是对“锁对象”的抢占——线程必须获取锁对象的monitor(管程)才能执行同步代码。
1. 修饰实例方法(锁:当前类的实例对象)
- 锁对象:this(当前调用方法的实例);
- 特点:不同实例的锁相互独立,同一实例的同步方法互斥。
示例代码:
public class SyncInstanceMethod {
// 修饰实例方法:锁是当前实例(this)
public synchronized void print(String name) {
for (int i = 0; i < 3; i++) {
System.out.println(name + ":" + i);
try {
Thread.sleep(500); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SyncInstanceMethod instance1 = new SyncInstanceMethod();
SyncInstanceMethod instance2 = new SyncInstanceMethod();
// 线程1:调用instance1的print(锁instance1)
new Thread(() -> instance1.print("线程1")).start();
// 线程2:调用instance1的print(锁instance1,与线程1互斥)
new Thread(() -> instance1.print("线程2")).start();
// 线程3:调用instance2的print(锁instance2,与线程1/2不互斥)
new Thread(() -> instance2.print("线程3")).start();
}
}
执行结果:
- 线程1和线程2交替执行(同一实例锁互斥);
- 线程3与线程1/2并行执行(不同实例锁独立)。
2. 修饰静态方法(锁:当前类的Class对象)
- 锁对象:类名.class(当前类的Class对象,全局唯一);
- 特点:所有实例共享这把锁,无论哪个实例调用静态同步方法,都互斥。
示例代码:
public class SyncStaticMethod {
// 修饰静态方法:锁是SyncStaticMethod.class
public static synchronized void staticPrint(String name) {
for (int i = 0; i < 3; i++) {
System.out.println(name + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SyncStaticMethod instance1 = new SyncStaticMethod();
SyncStaticMethod instance2 = new SyncStaticMethod();
// 线程1:通过instance1调用静态方法(锁Class对象)
new Thread(() -> instance1.staticPrint("线程1")).start();
// 线程2:通过instance2调用静态方法(同一把Class锁,互斥)
new Thread(() -> instance2.staticPrint("线程2")).start();
}
}
执行结果:线程1执行完3次后,线程2才开始执行(Class锁全局唯一)。
3. 修饰代码块(锁:自定义对象,最灵活)
- 锁对象:手动指定(可以是this、类Class对象、任意自定义对象);
- 特点:精准控制同步范围,减少锁粒度,提升性能。
示例代码(3种锁对象):
public class SyncCodeBlock {
// 自定义锁对象(推荐:私有、不可变)
private final Object lockObj = new Object();
// 锁:this(当前实例)
public void blockThis(String name) {
synchronized (this) {
print(name);
}
}
// 锁:Class对象(全局锁)
public void blockClass(String name) {
synchronized (SyncCodeBlock.class) {
print(name);
}
}
// 锁:自定义对象(局部锁)
public void blockCustom(String name) {
synchronized (lockObj) {
print(name);
}
}
private void print(String name) {
for (int i = 0; i < 3; i++) {
System.out.println(name + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SyncCodeBlock instance = new SyncCodeBlock();
// 线程1:锁this
new Thread(() -> instance.blockThis("线程1")).start();
// 线程2:锁Class对象(与线程1不互斥,锁对象不同)
new Thread(() -> instance.blockClass("线程2")).start();
// 线程3:锁自定义对象(与线程1/2不互斥)
new Thread(() -> instance.blockCustom("线程3")).start();
}
}
执行结果:三个线程并行执行(锁对象不同,无互斥)。
二、synchronized的核心特性(理解锁行为)
掌握用法后,需理解synchronized的核心特性,这是区分它与其他锁(如ReentrantLock)的基础。
1. 可重入性(避免同一线程死锁)
定义:同一线程多次获取同一把锁时,不会死锁,锁会记录“重入次数”,释放次数需与获取次数一致。
- 底层实现:JVM通过monitor的“拥有者线程”和“重入计数器”实现(计数器初始0,获取锁+1,释放锁-1,计数器为0时真正释放)。
示例代码(验证可重入):
public class SyncReentrant {
public synchronized void method1() {
System.out.println("method1:获取锁,计数器=1");
method2(); // 同一线程再次获取同一把锁(this)
}
public synchronized void method2() {
System.out.println("method2:重入锁,计数器=2");
method3();
}
private synchronized void method3() {
System.out.println("method3:重入锁,计数器=3");
}
public static void main(String[] args) {
new SyncReentrant().method1(); // 无死锁,正常执行
}
}
执行结果:三个方法依次执行,无死锁,验证了可重入性。
2. 不可中断性(等待锁时无法被中断)
定义:线程等待获取synchronized锁时,会进入“阻塞状态”,即使调用Thread.interrupt()标记中断状态,也无法唤醒线程,只能继续等待锁释放。
示例代码(验证不可中断):
public class SyncUninterruptible {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// 线程1:先获取锁,持有10秒
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread.sleep(100); // 确保t1先获取锁
// 线程2:等待锁,尝试中断
Thread t2 = new Thread(() -> {
System.out.println("t2:开始等待锁...");
synchronized (lock) { // 进入阻塞,等待锁
System.out.println("t2:获取到锁(如能中断该行代码则不会执行)");
}
});
t2.start();
Thread.sleep(1000);
t2.interrupt(); // 中断t2
System.out.println("t2:中断状态=" + t2.isInterrupted()); // true,但仍等待锁
}
}
执行结果:t2中断状态为true,但仍会阻塞到t1释放锁,验证了不可中断性。
3. 非公平性(无法保证先来先得)
定义:synchronized是固定的非公平锁——新线程尝试获取锁时,会先“插队”(直接抢占monitor),而非排队;即使有线程在等待队列中,新线程也可能优先获取锁,无法保证等待线程的获取顺序。
- 底层缘由:JVM唤醒等待线程时,是从_EntryList(等待队列)中随机选择线程,而非按FIFO顺序。
三、synchronized的底层实现原理(JVM层面)
这是深度理解的核心,synchronized的底层依赖两个核心:对象头(Mark Word) 和 Monitor(管程)。
1. 前提:Java对象的内存布局
每个Java对象在堆内存中包含3部分:
对象头(Object Header):Mark Word + 类型指针(Klass Pointer)
实例数据(Instance Data):对象的成员变量
对齐填充(Padding):保证对象大小是8字节的整数倍
其中,Mark Word 是实现锁的核心,它存储了对象的锁状态、拥有者线程ID、重入计数器等信息,结构如下(32位JVM):
|
锁状态 |
存储内容(Mark Word) |
|
无锁状态 |
对象哈希码 + 分代年龄 + 无锁标记(01) |
|
偏向锁状态 |
偏向线程ID + 偏向时间戳 + 分代年龄 + 偏向标记(01) |
|
轻量级锁状态 |
指向栈中锁记录的指针 + 轻量级锁标记(00) |
|
重量级锁状态 |
指向Monitor的指针 + 重量级锁标记(10) |
|
标记为垃圾 |
空 + 垃圾标记(11) |
2. Monitor(管程)机制(核心)
synchronized的互斥性依赖Monitor(由JVM的C++层实现,对应ObjectMonitor类),每个对象都关联一个Monitor,只有获取到Monitor的线程才能执行同步代码。
ObjectMonitor的核心字段(简化):
// C++层面的ObjectMonitor结构(JVM源码)
class ObjectMonitor {
_owner; // 持有锁的线程(NULL表明无线程持有)
_EntryList; // 等待获取锁的线程队列(阻塞状态)
_WaitSet; // 调用wait()后等待的线程队列
_recursions; // 重入计数器(对应可重入性)
_count; // 锁的竞争次数
};
Monitor的核心工作流程:

3. 字节码层面的体现
synchronized编译后会生成monitorenter和monitorexit指令,分别对应“获取锁”和“释放锁”。
示例:反编译同步代码块的字节码
// 源码
public class SyncBytecode {
public void test() {
synchronized (this) {
System.out.println("同步代码块");
}
}
}
编译后通过javap -v SyncBytecode.class反编译,核心字节码:
public void test();
Code:
0: aload_0 // 加载this到操作数栈
1: dup // 复制this引用
2: astore_1 // 存储到局部变量表
3: monitorenter // 获取this对应的Monitor(加锁)
4: getstatic #2 // 获取System.out
7: ldc #3 // 加载字符串"同步代码块"
9: invokevirtual #4 // 调用println方法
12: aload_1 // 加载局部变量表中的this
13: monitorexit // 释放Monitor(解锁)
14: goto 22 // 正常结束,跳转到22行
17: astore_2 // 异常处理:存储异常
18: aload_1 // 加载this
19: monitorexit // 异常时也释放锁(避免死锁)
20: aload_2 // 抛出异常
21: athrow // 异常抛出
22: return // 方法返回
关键结论:
- monitorenter:线程尝试获取Monitor,失败则阻塞;
- monitorexit:释放Monitor,且编译器会在异常路径中也插入monitorexit(保证锁自动释放)。
四、JDK 1.6后的锁优化(性能提升核心)
JDK 1.6前,synchronized是“重量级锁”(直接调用操作系统互斥量,性能差);JDK 1.6引入了偏向锁、轻量级锁等优化,让synchronized性能大幅提升。
1. 锁状态升级流程(不可逆)
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- 升级触发条件:根据锁的竞争程度,从低开销到高开销逐步升级。
2. 各锁状态的核心逻辑
|
锁状态 |
适用场景 |
实现原理 |
性能特点 |
|
偏向锁 |
无竞争/单线程重复加锁 |
标记锁的偏向线程ID,后续该线程加锁无需CAS,直接获取 |
开销最小(仅标记ID) |
|
轻量级锁 |
少量线程竞争(短时间) |
线程通过CAS自旋获取锁,避免内核态切换 |
开销中等(CAS自旋) |
|
重量级锁 |
大量线程竞争(长时间) |
线程进入内核态阻塞,依赖操作系统互斥量 |
开销最大(上下文切换) |
3. 关键优化:自旋锁与适应性自旋
- 自旋锁:轻量级锁竞争时,线程不立即阻塞,而是通过CAS循环(自旋)尝试获取锁,减少内核态切换;
- 适应性自旋:JVM根据前一次自旋的成功率,动态调整自旋次数(如上次成功则增加次数,失败则减少),提升自旋效率。
五、实战注意事项(避坑)
1. 锁对象的选择(核心避坑点)
- ❌ 错误:使用String常量、Integer等包装类常量(如synchronized (“lock”))——常量池复用导致锁失效;
- ❌ 错误:使用可变对象(如synchronized (new ArrayList<>()))——每次创建新对象,锁失去互斥性;
- ✅ 正确:使用private final Object lock = new Object()(私有、不可变、专属锁对象)。
2. 锁粒度控制
- 锁粒度过大:同步整个方法(如public synchronized void bigMethod()),导致并发度低;
- 锁粒度过小:过度拆分同步代码块,可能导致线程安全问题;
- ✅ 原则:只同步“需要原子性的核心代码”,缩小同步范围。
3. 与volatile的区别(补充)
|
特性 |
synchronized |
volatile |
|
原子性 |
保证 |
不保证 |
|
可见性 |
保证 |
保证 |
|
有序性 |
保证 |
保证(禁止重排序) |
|
适用场景 |
同步代码块/方法 |
修饰变量(状态标记) |
总结(核心要点回顾)
- 使用层面:synchronized有3种用法(实例方法、静态方法、代码块),核心是“锁对象”——不同锁对象对应不同的互斥范围;
- 特性层面:具备可重入、不可中断、非公平性,异常时自动释放锁;
- 底层层面:依赖对象头(Mark Word)和Monitor机制,JDK 1.6后通过偏向锁/轻量级锁优化性能;
- 实战层面:锁对象需选私有不可变对象,控制锁粒度,避免常量锁/可变锁陷阱。
理解这些内容后,你不仅能正确使用synchronized,还能从底层解释其行为,在高并发场景下做出合理的锁设计。

