什么是 synchronized 方法?
synchronized 是 Java 中的一个关键字,用于实现线程间的同步,当一个方法被声明为 synchronized 时,它意味着:

在同一时间,只允许一个线程执行该方法。
如果一个线程正在执行一个对象的 synchronized 方法,那么其他所有线程(无论是尝试执行同一个对象的 synchronized 方法,还是执行该对象的另一个 synchronized 方法)都必须等待,直到第一个线程执行完毕。
这就像一个公共卫生间,门上锁了(synchronized),一个人进去后(线程进入方法),其他人只能在外面排队等待(线程阻塞),直到这个人出来(线程执行完毕),下一个人才能进去。
synchronized 方法的工作原理
synchronized 方法的实现依赖于 JVM 的内置锁(也称为监视器锁,Monitor Lock)。

- 获取锁:当一个线程要调用一个对象的
synchronized方法时,它必须首先获取该对象的内置锁。 - 执行方法:如果成功获取了锁,线程就可以执行该方法体内的代码,在执行期间,它一直持有这个锁。
- 释放锁:当线程执行完方法体(无论是正常结束还是因为异常退出),它就会自动释放该对象的内置锁。
- 等待唤醒:其他正在等待获取该锁的线程,此时就有机会去获取锁,从而进入方法执行。
核心要点:锁是与对象关联的,而不是与代码或方法关联的。
语法与使用
synchronized 关键字可以放在方法返回类型之前。
1 实例方法
这是最常见的用法,锁住的是当前对象实例(this)。
public class Counter {
private int count = 0;
// 锁住的是当前 Counter 对象实例 (this)
public synchronized void increment() {
count++;
System.out.println(Thread.currentThread().getName() + " - Count: " + count);
}
public int getCount() {
return count;
}
}
工作方式:

- 假设有两个线程
Thread-A和Thread-B,它们都操作同一个Counter对象counter。 - 当
Thread-A调用counter.increment()时,它会获取counter这个对象的锁。 - 如果
Thread-B也尝试调用counter.increment(),它会被阻塞,因为它无法获取counter对象的锁,必须等待Thread-A释放锁。 - 注意:
Thread-B调用的是counter的非同步方法(getCount()),它不会被阻塞,因为getCount()不需要锁。
2 静态方法
当 synchronized 作用于静态方法时,锁住的是该类的 Class 对象,而不是类的某个实例。
public class SharedResource {
// 锁住的是 SharedResource 这个类的 Class 对象
public static synchronized void staticMethod() {
// 代码...
}
}
工作方式:
- 假设有两个线程
Thread-A和Thread-B,它们分别操作SharedResource的两个不同实例resource1和resource2。 - 当
Thread-A调用resource1.staticMethod()时,它会获取SharedResource.class这个锁。 - 即使
Thread-B调用resource2.staticMethod(),它也会被阻塞,因为它也需要获取SharedResource.class这个锁,而这个锁已经被Thread-A持有了。 - 这与实例方法形成了鲜明对比,实例方法只会在同一个实例上产生互斥。
3 代码块(更灵活的方式)
虽然问题问的是 synchronized 方法,但必须提到 synchronized 代码块,因为它更灵活、性能通常更好。
语法:synchronized (锁对象) { ... }
锁对象可以是任意对象,但必须是同一个对象才能起到同步作用。
public class Counter {
private int count = 0;
private final Object lock = new Object(); // 创建一个专门的锁对象
public void increment() {
// 只对需要同步的代码块加锁,而不是整个方法
synchronized (lock) { // 锁住我们创建的 lock 对象
count++;
}
// ... 其他不需要同步的代码
}
}
优点:
- 粒度更细:只有真正需要保证原子性的代码才会被同步,减少了锁的持有时间,提高了并发性能。
- 灵活性:可以指定任意对象作为锁,而局限于
this或Class对象。
一个完整的示例
下面这个例子清晰地展示了 synchronized 方法的互斥效果。
public class SynchronizedMethodExample {
public static void main(String[] args) {
// 创建一个共享资源对象
Counter counter = new Counter();
// 创建两个线程,操作同一个 counter 对象
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
}, "Thread-A");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
}, "Thread-B");
// 启动线程
thread1.start();
thread2.start();
// 等待两个线程执行完毕
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 最终结果应该是 2000
System.out.println("Final count: " + counter.getCount());
}
}
class Counter {
private int count = 0;
// synchronized 确保了 count++ 的原子性
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
运行结果:
Thread-A - Count: 1
Thread-B - Count: 2
...
Final count: 2000
如果没有 synchronized,由于 count++ 不是原子操作(包含读取、修改、写回三个步骤),最终结果几乎肯定小于 2000。
优缺点
优点
- 使用简单:只需在方法前加一个关键字,JVM 会自动处理锁的获取和释放。
- 保证原子性:确保被同步的代码块作为一个不可分割的单元执行。
- 可见性:由
synchronized保护的内存区域,当一个线程释放锁时,它在该临界区内所做的所有修改都会对后续获取该锁的线程可见。
缺点
-
性能开销:
- 锁获取:获取和释放锁需要额外的 CPU 时间。
- 线程阻塞:如果一个线程长时间持有锁,其他线程就必须等待,导致上下文切换,这会带来显著的性能开销。
-
可能导致死锁:如果多个线程相互等待对方持有的锁,就会导致所有线程都永久阻塞,程序无法继续执行。
// 死锁示例 final Object lock1 = new Object(); final Object lock2 = new Object(); new Thread(() -> { synchronized (lock1) { System.out.println("Thread 1: Holding lock 1..."); try { Thread.sleep(100); } catch (Exception e) {} System.out.println("Thread 1: Waiting for lock 2..."); synchronized (lock2) { /* ... */ } } }).start(); new Thread(() -> { synchronized (lock2) { System.out.println("Thread 2: Holding lock 2..."); try { Thread.sleep(100); } catch (Exception e) {} System.out.println("Thread 2: Waiting for lock 1..."); synchronized (lock1) { /* ... */ } } }).start(); -
不可中断:一个线程在等待获取锁时,它不能被中断(
Thread.interrupt()不会生效),只能一直等待。
总结与最佳实践
| 特性 | synchronized 方法 |
synchronized 代码块 |
|---|---|---|
| 锁对象 | 实例方法:this静态方法: Class 对象 |
任意指定的对象 |
| 粒度 | 粗粒度(整个方法) | 细粒度(只同步代码块) |
| 性能 | 较低(锁持有时间长) | 较高(锁持有时间短) |
| 灵活性 | 低 | 高 |
| 可读性 | 高,简单明了 | 中等,需要理解锁对象 |
最佳实践:
- 首选
synchronized代码块:
