目录
- 核心概念:什么是
synchronized? synchronized的三种使用方式- 修饰实例方法
- 修饰静态方法
- 修饰代码块
- 底层原理:
synchronized是如何工作的?- Java 对象头与 Monitor
- 锁升级过程(偏向锁 -> 轻量级锁 -> 重量级锁)
synchronized的特性- 互斥性
- 可见性
- 有序性
- 可重入性
- 优缺点总结
- 与
java.util.concurrent.locks.Lock的比较 - 最佳实践
核心概念:什么是 synchronized?
synchronized 关键字可以保证在同一时刻,只有一个线程可以执行被 synchronized 修饰的代码块或方法,这种机制被称为互斥或互斥锁。

它的主要目的是保证线程安全,防止多个线程同时操作共享数据时,导致数据不一致或损坏的问题。
核心思想: 当一个线程获取了某个对象的锁后,其他线程试图获取该对象的锁时,将被阻塞,直到持有锁的线程释放锁。
synchronized 的三种使用方式
a. 修饰实例方法
当 synchronized 修饰一个非静态方法时,它锁定的是当前对象实例(this)。
public class SynchronizedExample {
public synchronized void instanceMethod() {
// 临界区代码
System.out.println(Thread.currentThread().getName() + " is running instanceMethod.");
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " finished instanceMethod.");
}
}
工作原理:

- 假设有两个线程
Thread-A和Thread-B,它们都创建了一个SynchronizedExample的实例example。 - 当
Thread-A调用example.instanceMethod()时,它会获取example这个对象的锁。 - 如果
Thread-B也想调用example.instanceMethod(),它必须等待Thread-A执行完毕并释放锁。 - 注意:
Thread-B创建了一个新的实例example2,它可以直接调用example2.instanceMethod(),因为它们操作的是不同的对象锁,不会互相阻塞。
b. 修饰静态方法
当 synchronized 修饰一个静态方法时,它锁定的是当前类的 Class 对象,而不是实例对象。
public class SynchronizedExample {
public static synchronized void staticMethod() {
// 临界区代码
System.out.println(Thread.currentThread().getName() + " is running staticMethod.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " finished staticMethod.");
}
}
工作原理:
- 静态方法属于类,而不属于任何实例,所有实例共享同一个类级别的锁。
- 无论有多少个
SynchronizedExample的实例,在任何时候,都只有一个线程能执行SynchronizedExample.staticMethod()。 Thread-A通过实例example1调用静态方法,Thread-B通过实例example2调用同一个静态方法,Thread-B必须等待Thread-A执行完毕。
c. 修饰代码块
这是 synchronized 最灵活、也是推荐使用的方式,你可以明确指定要锁定的对象。
public class SynchronizedExample {
private final Object lock = new Object(); // 一个专用的锁对象
public void blockMethod() {
// 锁定 this 对象
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " is running in synchronized(this) block.");
}
// 锁定一个特定的对象
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " is running in synchronized(lock) block.");
}
// 锁定类的 Class 对象(效果等同于修饰静态方法)
synchronized (SynchronizedExample.class) {
System.out.println(Thread.currentThread().getName() + " is running in synchronized(Class) block.");
}
}
}
为什么推荐使用代码块?

- 减少锁的粒度:相比于锁定整个方法,锁定代码块可以只保护真正需要同步的代码,而方法中其他非同步代码可以并发执行,提高了性能。
- 灵活性:可以指定任意对象作为锁,从而实现更复杂的同步逻辑,可以为不同的资源使用不同的锁,减少线程间的竞争。
底层原理:synchronized 是如何工作的?
synchronized 的实现与 Java 对象内存布局息息相关。
a. Java 对象头与 Monitor
在 HotSpot 虚拟机中,对象在内存中分为三部分:
- 对象头:包含 Mark Word 和类型指针。
- Mark Word:存储了对象的运行时数据,如哈希码、GC分代年龄、以及锁状态标志等。
synchronized的锁信息就存储在这里。 - 类型指针:指向该对象的类元数据。
- Mark Word:存储了对象的运行时数据,如哈希码、GC分代年龄、以及锁状态标志等。
- 实例数据:对象真正存储的有效信息。
- 对齐填充:用于确保对象大小是某个字节的整数倍(8 字节)。
synchronized 依赖的是监视器锁,每个 Java 对象都可以关联一个监视器,当线程试图获取一个对象的锁时,它实际上是在尝试获取该对象关联的监视器的所有权。
monitorenter:JVM 指令,当线程执行到synchronized代码块时,会尝试获取对象的监视器,如果获取成功,计数器加 1;如果失败,线程进入阻塞状态。monitorexit:JVM 指令,当线程退出synchronized代码块时,会释放监视器,计数器减 1,计数器为 0 时,其他线程才有机会获取该监视器。
b. 锁升级过程(JDK 1.6 优化)
为了提高性能,synchronized 的锁不是一成不变的,它会根据竞争情况在多个状态间升级,这个过程被称为偏向锁和轻量级锁的优化。
- 无锁状态
- 偏向锁:
- 场景:一个锁总是被同一个线程多次获取。
- 原理:Mark Word 中存储的是偏向线程 ID,线程获取锁时,只需进行 CAS 操作,将线程 ID 写入 Mark Word,整个过程没有阻塞,开销极小。
- 升级:当另一个线程尝试获取这个偏向锁时,偏向锁会撤销,升级为轻量级锁。
- 轻量级锁:
- 场景:锁存在一定程度的竞争,但竞争不激烈。
- 原理:竞争线程不会阻塞,而是通过自旋的方式尝试获取锁,自旋就是在一个循环中不断尝试获取锁,避免从用户态切换到内核态的开销。
- 缺点:如果自旋时间过长(10 次左右),会消耗 CPU 资源。
- 升级:如果自旋一定次数后仍未获取到锁,或者自旋时又有新线程来竞争,锁会升级为重量级锁。
- 重量级锁:
- 场景:锁竞争非常激烈。
- 原理:未获取到锁的线程会被阻塞,并放入等待队列中,当持有锁的线程释放锁时,会通过
park()/unpark()机制唤醒一个等待线程。 - 缺点:线程的阻塞和唤醒需要从用户态切换到内核态,开销巨大。
锁升级路径: 偏向锁 -> 轻量级锁 -> 重量级锁,这个过程是不可逆的。
synchronized 的特性
a. 互斥性
同一时间,只有一个线程能持有锁,保证了共享资源在同一时刻只被一个线程访问。
b. 可见性
synchronized 的一个重要特性是保证内存可见性,当线程释放锁时,JMM(Java 内存模型)会强制将该线程工作内存中的所有共享变量刷新到主内存中,当线程获取锁时,JMM 会强制将该线程工作内存置为无效,从而需要从主内存中重新加载共享变量,这确保了锁的获取和释放过程对其他线程是
