什么是 synchronized 方法?
synchronized 关键字在 Java 中用于实现互斥锁(Mutex),它是一种内置的锁机制,也称为监视器锁(Monitor Lock)。

当一个方法被声明为 synchronized 时,意味着:
在同一时刻,只允许一个线程执行该方法。
如果一个线程正在执行一个 synchronized 实例方法,那么其他所有线程都将被阻塞,直到该线程执行完毕并释放锁,其他线程才能有机会获取该锁并进入该方法。
synchronized 方法的两种形式
synchronized 可以用在实例方法和静态方法上,但它们锁定的对象是不同的,这是理解其行为的关键。
1 实例方法
public class MyObject {
// synchronized 实例方法
public synchronized void instanceMethod() {
// 方法体...
System.out.println(Thread.currentThread().getName() + " is running instanceMethod.");
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
-
锁定的对象:
this,也就是当前实例对象。
(图片来源网络,侵删) -
工作原理:假设我们有两个线程
Thread-A和Thread-B,它们都操作同一个MyObject的实例(myObject)。- 当
Thread-A调用myObject.instanceMethod()时,它会尝试获取myObject这个对象的锁。 - 一旦获取成功,
Thread-A进入方法体执行。 - 如果
Thread-B也调用myObject.instanceMethod(),它会尝试获取同一个myObject对象的锁,但发现锁已经被Thread-A占用,Thread-B会被阻塞,等待锁被释放。 - 只有当
Thread-A执行完方法并退出时,它才会自动释放myObject的锁,Thread-B才能获取锁并进入方法。
- 当
-
关键点:只有操作同一个对象实例的
synchronized实例方法,它们之间才会互斥,如果两个线程操作的是两个不同的MyObject实例,它们可以同时各自执行自己的instanceMethod,因为它们锁定的是不同的对象。
2 静态方法
public class MyObject {
// synchronized 静态方法
public static synchronized void staticMethod() {
// 方法体...
System.out.println(Thread.currentThread().getName() + " is running staticMethod.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
-
锁定的对象:当前类的
Class对象(在 JVM 中,每个类都会有一个唯一的Class对象)。 -
工作原理:
(图片来源网络,侵删)Class对象在 JVM 中是全局唯一的,无论你创建多少个该类的实例,它的Class对象都只有一个。- 当任何线程调用
MyObject.staticMethod()时,它会尝试获取MyObject.class这个对象的锁。 - 一旦获取成功,线程进入方法体执行。
- 其他任何线程(无论是否操作了
MyObject的实例)只要再调用MyObject.staticMethod(),都会因为无法获取到MyObject.class的锁而被阻塞。
-
关键点:
synchronized静态方法锁的是类级别的锁,而不是实例级别的锁,这意味着所有对该类静态方法的调用都会互斥,与实例无关。
代码示例
让我们通过一个例子来直观感受这两种锁的区别。
public class SynchronizedMethodDemo {
public static void main(String[] args) {
// --- 场景1:测试 synchronized 实例方法 ---
System.out.println("--- 场景1:测试 synchronized 实例方法 ---");
MyInstanceObject instance = new MyInstanceObject();
// 两个线程操作同一个实例
new Thread(instance::instanceMethod, "Thread-A-Instance").start();
new Thread(instance::instanceMethod, "Thread-B-Instance").start();
try {
Thread.sleep(3000); // 等待上面的线程执行完
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n--- 场景2:测试 synchronized 静态方法 ---");
// --- 场景2:测试 synchronized 静态方法 ---
// 两个线程操作不同的实例,但调用的是静态方法
new Thread(MyInstanceObject::staticMethod, "Thread-A-Static").start();
new Thread(MyInstanceObject::staticMethod, "Thread-B-Static").start();
}
}
class MyInstanceObject {
// synchronized 实例方法
public synchronized void instanceMethod() {
System.out.println(Thread.currentThread().getName() + " 开始执行实例方法,时间: " + System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 执行完实例方法,时间: " + System.currentTimeMillis());
}
// synchronized 静态方法
public static synchronized void staticMethod() {
System.out.println(Thread.currentThread().getName() + " 开始执行静态方法,时间: " + System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 执行完静态方法,时间: " + System.currentTimeMillis());
}
}
预期输出分析:
-
场景1输出:
--- 场景1:测试 synchronized 实例方法 --- Thread-A-Instance 开始执行实例方法,时间: 167... // (等待2秒后) Thread-A-Instance 执行完实例方法,时间: 167... Thread-B-Instance 开始执行实例方法,时间: 167... // (再等待2秒后) Thread-B-Instance 执行完实例方法,时间: 167...Thread-B-Instance必须等待Thread-A-Instance完全执行完毕后才能开始,证明了实例方法锁的是this对象。 -
场景2输出:
--- 场景2:测试 synchronized 静态方法 --- Thread-A-Static 开始执行静态方法,时间: 167... // (等待2秒后) Thread-A-Static 执行完静态方法,时间: 167... Thread-B-Static 开始执行静态方法,时间: 167... // (再等待2秒后) Thread-B-Static 执行完静态方法,时间: 167...同样,
Thread-B-Static必须等待Thread-A-Static完全执行完毕,即使它们操作的是不同的MyInstanceObject实例(在main方法中可以创建两个不同的实例来调用),它们依然会互斥,因为静态方法锁的是MyInstanceObject.class。
synchronized 方法的优缺点
优点
- 使用简单:语法非常直观,只需在方法签名前加上
synchronized关键字。 - 内置锁管理:JVM 会自动处理锁的获取和释放,当一个线程进入
synchronized方法时,它会自动获取锁;当方法正常返回或抛出异常时,它会自动释放锁,这避免了手动加锁和解锁可能带来的死锁或忘记解锁的问题。 - 可重入性:Java 的内置锁是可重入的,这意味着一个已经持有锁的线程,可以再次进入由同一个锁保护的代码,一个
synchronized方法可以调用同一个对象的另一个synchronized方法,而不会导致自己被阻塞。
缺点
- 性能开销:相比于
java.util.concurrent.locks.ReentrantLock,synchronized的性能(在 JDK 1.6 之前)通常较差,锁的获取和释放需要从用户态切换到内核态,这是一个相对耗时的操作,从 JDK 1.6 开始,JVM 对synchronized进行了大量优化(如偏向锁、轻量级锁、锁消除等),其性能已经得到了巨大提升,在很多场景下与ReentrantLock不相上下。 - 功能有限:
- 无法中断:一个线程在等待获取锁时,它不能被中断,它必须一直等待,直到其他线程释放锁。
- 无法设置超时:无法像
ReentrantLock.tryLock(long time, TimeUnit unit)那样设置一个超时时间,避免无限等待。 - 非公平锁:
synchronized是非公平锁
