synchronized 是什么?
synchronized 是 Java 中的一个关键字,它主要有两种作用:

- 保证原子性:确保在同一时刻,只有一个线程可以执行被
synchronized修饰的代码块或方法,这就像给一段代码上了一把“锁”,只有拿到钥匙的线程才能进入执行。 - 保证可见性:当一个线程释放锁时,JMM(Java 内存模型)会强制将该线程工作内存中的共享变量刷新回主内存,当另一个线程获取同一个锁时,JMM 会强制将该线程的工作内存置为无效,从而需要从主内存中重新加载共享变量,这样就保证了线程间的可见性。
synchronized 是 Java 内置的、最基础的锁机制,用于解决多线程环境下的竞态条件问题。
synchronized 的三种使用方式
synchronized 可以用在三个地方:实例方法、静态方法和代码块。
修饰实例方法(对象锁)
当 synchronized 修饰一个非静态的实例方法时,它锁定的是当前对象实例(this)。
public class SynchronizedExample {
public synchronized void instanceMethod() {
// 锁定的是 this 对象
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.");
}
}
特点:

- 锁是对象级别的,如果多个线程调用的是同一个对象的
instanceMethod,那么它们会互斥执行。 - 如果线程调用的是不同对象的
instanceMethod,它们不会互斥,因为锁的对象不同。
SynchronizedExample e1 = new SynchronizedExample(); SynchronizedExample e2 = new SynchronizedExample(); new Thread(() -> e1.instanceMethod(), "Thread-A").start(); // 会获取 e1 的锁 new Thread(() -> e1.instanceMethod(), "Thread-B").start(); // 会阻塞,等待 e1 的锁 new Thread(() -> e2.instanceMethod(), "Thread-C").start(); // 会获取 e2 的锁,与 Thread-A 不冲突
修饰静态方法(类锁)
当 synchronized 修饰一个静态方法时,它锁定的是当前类的 Class 对象(SynchronizedExample.class)。
public class SynchronizedExample {
public static synchronized void staticMethod() {
// 锁定的是 SynchronizedExample.class 对象
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.");
}
}
特点:
- 锁是类级别的,无论你创建了多少个该类的实例,所有线程在调用这个静态方法时,竞争的都是同一个
Class对象的锁。 - 所有调用该静态方法的线程都会互斥执行。
SynchronizedExample e1 = new SynchronizedExample(); SynchronizedExample e2 = new SynchronizedExample(); new Thread(() -> e1.staticMethod(), "Thread-A").start(); // 会获取 SynchronizedExample.class 的锁 new Thread(() -> SynchronizedExample.staticMethod(), "Thread-B").start(); // 会阻塞,等待 SynchronizedExample.class 的锁 new Thread(() -> e2.staticMethod(), "Thread-C").start(); // 也会阻塞,等待同一个类锁
修饰代码块(灵活指定锁)
这是最灵活、最推荐的方式,你可以明确指定要锁定的对象,可以是任意对象,也可以是 this 或 ClassName.class。
public class SynchronizedExample {
private final Object lock = new Object(); // 通常使用一个 final 的对象作为锁
public void blockMethod() {
// 锁定 this 对象
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " is running synchronized(this).");
// ...
}
// 锁定类的 Class 对象
synchronized (SynchronizedExample.class) {
System.out.println(Thread.currentThread().getName() + " is running synchronized(ClassName.class).");
// ...
}
// 锁定一个自定义的锁对象
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " is running synchronized(customLock).");
// ...
}
}
}
特点:

- 粒度更细:你不需要锁定整个方法,只需要锁定方法中需要同步的关键代码段,减少锁的持有时间,提高并发性。
- 灵活性高:可以选择任意对象作为锁,实现更复杂的同步逻辑。
synchronized 的底层原理(JDK 1.6 之后优化)
synchronized 的实现依赖于 JVM,在早期版本(JDK 1.6 之前),它是一种重量级锁,性能较差,但从 JDK 1.6 开始,JVM 对 synchronized 进行了大量优化,引入了锁升级机制,使其性能大幅提升。
锁的升级过程是一个不可逆的过程:
-
偏向锁
- 场景:当一个锁被一个线程获取后,之后总是由这个线程来获取。
- 原理:线程 ID 被记录在对象的 Mark Word 中,当该线程再次获取锁时,无需进行 CAS 操作加锁,只需检查 Mark Word 中的线程 ID 是否是自己即可,这是一种“无锁”状态,开销极小。
- 适用:单线程或几乎没有竞争的场景。
-
轻量级锁
- 场景:当有另一个线程尝试获取一个已经被偏向锁持有的锁时,偏向锁会升级为轻量级锁。
- 原理:竞争线程会通过自旋 的方式尝试获取锁,自旋就是让线程空转几个循环,而不是立即阻塞,因为线程的阻塞和唤醒(从用户态到内核态的切换)是非常耗时的操作,如果在自旋期间获取到了锁,就避免了阻塞。
- 适用:竞争不激烈,锁持有时间很短的场景。
-
重量级锁
- 场景:当自旋一定次数后(默认 10 次),仍然没有获取到锁,或者有多个线程在竞争同一个锁时,轻量级锁会升级为重量级锁。
- 原理:未获取到锁的线程会被挂起,进入阻塞状态,当持有锁的线程释放锁时,会唤醒一个阻塞的线程,这个过程涉及操作系统内核的调度,开销很大。
- 适用:竞争激烈,锁持有时间较长的场景。
synchronized 的设计非常智能,它会根据竞争情况自动选择最合适的锁状态,从而在大多数情况下都能获得不错的性能。
synchronized 的特性与注意事项
特性
-
可重入性:一个线程可以多次获取它已经持有的锁,这可以防止线程自己死锁自己。
public class Reentrant { public synchronized void outer() { System.out.println("Outer method"); inner(); // 可以再次调用同步方法 } public synchronized void inner() { System.out.println("Inner method"); } } -
不可中断性:一个线程在等待获取锁时,它不能被中断。
threadA正在等待threadB释放锁,即使你调用threadA.interrupt(),threadA也不会被中断,它会一直等待下去,这是synchronized相比Lock接口的一个缺点。
注意事项
-
锁对象不能为 null:如果锁对象是
null,会抛出NullPointerException。 -
避免锁的范围过大:只同步必要的代码块,不要将整个方法都同步,尤其是包含 I/O 操作、复杂计算或网络调用的方法。
-
不同的锁对象不互斥:一定要确保多个线程竞争的是同一个锁对象。
-
死锁:如果多个线程互相等待对方持有的锁,而没有外部干预,它们将永远等待下去。
final Object lockA = new Object(); final Object lockB = new Object(); new Thread(() -> { synchronized (lockA) { try { Thread.sleep(100); } catch (Exception e) {} synchronized (lockB) { /* ... */ } } }).start(); new Thread(() -> { synchronized (lockB) { try { Thread.sleep(100); } catch (Exception e) {} synchronized (lockA) { /* ... */ } } }).start();
synchronized vs. java.util.concurrent.locks.Lock
| 特性 | synchronized |
Lock (如 ReentrantLock) |
|---|---|---|
| 实现方式 | JVM 关键字,JVM 层面实现 | API,在 Java 代码层面实现 |
| 锁的获取 | 非阻塞,获取不到锁时会立即返回 | lock() 是阻塞的;tryLock() 是非阻塞的 |
| 锁的释放 | 自动释放,异常时 JVM 会自动释放 | 必须在 finally 块中手动释放 unlock() |
| 可中断性 | 不可中断 | 可中断(lockInterruptibly()) |
| 公平性 | 非公平锁 | 可以选择公平锁或非公平锁 |
| 条件变量 | 一个锁只能配一个条件(wait/notify) |
一个锁可以绑定多个 Condition 对象 |
| 灵活性 | 锁的范围只能是方法或代码块 | 可以实现更复杂的锁逻辑(如尝试获取、超时获取) |
- 对于绝大多数同步场景,
synchronized是简单、高效且足够好的选择。 - 当你需要更高级的功能,如可中断、公平锁、多条件变量或超时获取锁时,才应该考虑使用
Lock接口及其实现类(如ReentrantLock)。
希望这份详细的解释能帮助你彻底理解 Java 的 synchronized!
