目录
synchronized是什么?synchronized的基本使用方式- 修饰实例方法
- 修饰静态方法
- 修饰代码块
synchronized的底层原理- 监视器锁
- 对象头与 Mark Word
- 锁升级过程(偏向锁 -> 轻量级锁 -> 重量级锁)
synchronized的特点synchronized与ReentrantLock的比较
synchronized 是什么?
synchronized 是 Java 中的一个关键字,它提供了内置的锁(也称为监视器锁,Monitor Lock)机制,用于解决多线程环境下的线程安全问题。

当多个线程同时访问一个共享资源时,可能会引发“竞态条件”(Race Condition),导致数据不一致或程序错误。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();
}
}
}
// 测试
SynchronizedExample instance = new SynchronizedExample();
new Thread(() -> instance.instanceMethod(), "Thread-A").start();
new Thread(() -> instance.instanceMethod(), "Thread-B").start();
分析:
- 线程 A 和线程 B 调用的是同一个对象
instance的instanceMethod()。 - 因为锁的是
this(即instance对象),所以当线程 A 进入方法后,线程 B 必须等待,直到线程 A 执行完毕并释放锁。 - 输出结果: Thread-A 会先执行,2 秒后 Thread-B 才会执行,它们不会同时运行。
b. 修饰静态方法
当 synchronized 修饰一个静态方法时,它锁定的对象是当前类的 Class 对象(SynchronizedExample.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();
}
}
}
// 测试
new Thread(SynchronizedExample::staticMethod, "Thread-C").start();
new Thread(SynchronizedExample::staticMethod, "Thread-D").start();
分析:
- 锁的是
SynchronizedExample.class这个对象。 - 无论创建多少个
SynchronizedExample实例,它们的Class对象都是同一个。 - 线程 C 和线程 D 会互斥执行,一个执行完后另一个才能执行。
- 输出结果: Thread-C 和 Thread-D 也会串行执行,间隔约 2 秒。
c. 修饰代码块
这是最灵活的一种方式,可以明确指定要锁定的对象。

语法: synchronized (锁对象) { ... }
- 锁定实例对象:
synchronized (this) { ... },效果与修饰实例方法相同。 - 锁定任意对象:
synchronized (lockObject) { ... },可以自定义一个对象作为锁。 - 锁定
Class对象:synchronized (ClassName.class) { ... },效果与修饰静态方法相同。
public class SynchronizedExample {
private final Object lock = new Object(); // 自定义锁对象
public void blockMethod() {
// 锁定 this 对象
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " is running synchronized block on 'this'.");
}
// 锁定自定义对象 lock
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " is running synchronized block on 'lock'.");
}
// 锁定 Class 对象
synchronized (SynchronizedExample.class) {
System.out.println(Thread.currentThread().getName() + " is running synchronized block on 'Class'.");
}
}
}
最佳实践:
- 永远不要用字符串常量(
"lock")或this作为锁,因为字符串常量在 JVM 中具有全局唯一性,容易导致死锁或与其他代码产生意外的锁竞争。 - 推荐使用
private final Object lock = new Object();这样的私有对象作为锁,可以精确控制锁的粒度,避免误操作。
synchronized 的底层原理
synchronized 的实现与 Java 对象内存布局密切相关。
a. 监视器锁
synchronized 使用的锁是监视器锁,每个 Java 对象都可以关联一个监视器,当线程试图获取一个对象的监视器锁时,它必须先成功获取该锁,才能执行同步代码块,执行完毕后,释放监视器锁。
b. 对象头与 Mark Word
在 HotSpot JVM 中,对象在内存中分为三部分:对象头、实例数据和对齐填充。
- 对象头:包含了运行时数据,如哈希码、GC 分代年龄、锁状态信息等。
- Mark Word:是对象头的一部分,它存储了关于锁的关键信息,锁的状态就记录在 Mark Word 中。
c. 锁升级过程
为了提高性能,JVM 对 synchronized 锁做了优化,引入了锁升级的概念,锁会根据竞争情况,从低级到高级逐步升级,这个过程是不可逆的。
-
偏向锁
- 场景: 一个锁总是被同一个线程多次获取。
- 原理: Mark Word 中会记录“偏向线程 ID”,当线程再次获取锁时,无需 CAS(Compare-And-Swap)操作,只需检查 Mark Word 中的线程 ID 是否是自己的即可,开销极小。
- 优点: 无竞争时,性能最高。
-
轻量级锁
- 场景: 当有另一个线程竞争该锁时,偏向锁会撤销,升级为轻量级锁。
- 原理: 竞争线程会通过自旋(循环尝试获取锁)的方式来获取锁,自旋不会让线程阻塞,避免了从用户态到内核态的切换开销。
- 优点: 避免了线程阻塞和唤醒的开销,适用于竞争时间很短的场景。
- 缺点: 如果自旋时间过长(通常超过 10 次),会消耗大量 CPU 资源。
-
重量级锁
- 场景: 当自旋一定次数后仍未获取到锁,或者有多个线程竞争同一个锁时,轻量级锁会膨胀为重量级锁。
- 原理: 未获取到锁的线程会被阻塞,并放入等待队列中,当锁被释放时,会唤醒一个等待的线程。
- 优点: 不会消耗 CPU 进行自旋。
- 缺点: 线程阻塞和唤醒需要从用户态切换到内核态,开销巨大。
锁升级路径是:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁,这个过程是为了在无竞争或低竞争时获得高性能,在高竞争时保证系统的稳定性。
synchronized 的特点
- 互斥性: 保证在同一时刻,只有一个线程能进入同步代码块或方法。
- 可见性:
synchronized不仅保证了原子性,还保证了可见性,当一个线程释放锁时,JMM(Java Memory Model)会强制将该线程工作内存中的所有共享变量刷新到主内存中,当另一个线程获取锁时,会强制将该线程的工作内存置为无效,从而从主内存中重新加载共享变量,这确保了线程间变量的可见性。 - 可重入性: 一个已经获取了锁的线程,可以再次获取该锁,这避免了由自己引起的死锁。
public class ReentrantDemo { public synchronized void methodA() { System.out.println("进入 methodA"); methodB(); // 可以再次调用同一个对象的同步方法 }
