目录

synchronized 是什么

synchronized 基于 Java 对象的内置锁(intrinsic lock),也称监视器锁(monitor lock)。每个对象都关联一把内置锁。

同一时刻只能有一个线程持有同一把锁。线程释放锁之前的操作,happens-before 另一个线程后续获取同一把锁之后的操作。

因此,面试中分析 synchronized 只需先回答一个问题:两段代码锁的是不是同一个对象?

synchronized 的三种用法

用法 锁对象
修饰实例方法 当前实例,即 this
修饰静态方法 当前类的 Class 对象
修饰代码块 synchronized 括号中的对象

实例同步方法

实例同步方法锁的是调用方法的对象,与 synchronized (this) 等价。同一实例上的同步方法会互斥,不同实例之间不会。

class Counter {
    private int count;

    synchronized void increment() {
        count++;
    }

    int get() {
        synchronized (this) {
            return count;
        }
    }
}

静态同步方法

静态同步方法锁的是类对象,例如 Counter.class。无论通过哪个实例调用,争用的都是同一把类锁。

class Sequence {
    private static int value;

    static synchronized int next() {
        return ++value;
    }
}

同步代码块

同步代码块可以显式指定锁对象,并将临界区缩小到真正需要互斥的代码。

class Counter {
    private final Object lock = new Object();
    private int count;

    void increment() {
        synchronized (lock) {
            count++;
        }
    }
}

[!yellow]
专用锁对象通常应声明为 private final,避免锁引用被替换,也避免外部代码持有同一把锁。

synchronized 的特性

互斥性

同一把锁保护的临界区不会被多个线程同时执行。因此 count++ 这类读-改-写复合操作,在所有访问都遵守同一把锁时不会丢失更新。

可见性与有序性

释放某把锁,happens-before 后续获取同一把锁。这保证前一个线程在临界区中的写入,对后一个获取同一把锁的线程可见。

可重入

线程可以重复获取自己已经持有的锁。因此一个同步方法可以调用同一对象的另一个同步方法,不会把自己阻塞。

自动释放锁

同步代码正常结束或异常退出时,JVM 都会自动释放内置锁。这与必须在 finally 中手动 unlock()ReentrantLock 不同。

对象锁与类锁

场景 是否互斥 原因
同一对象的两个实例同步方法 都锁同一个 this
不同对象的实例同步方法 两个对象对应两把锁
同一类的两个静态同步方法 都锁同一个 Class 对象
实例同步方法与静态同步方法 一个锁实例,一个锁 Class 对象
同步方法与未同步的普通方法 普通方法不获取锁

synchronized 与 volatile

对比项 synchronized volatile
作用位置 方法或代码块 成员变量
互斥性
可见性
适用场景 保护多步复合操作或多个关联状态 单次读写的状态标记

volatile 不提供互斥,因此不能单独保证 count++ 这类复合操作的原子性。

synchronized 与 ReentrantLock

对比项 synchronized ReentrantLock
锁的释放 JVM 自动释放 必须手动 unlock()
等待可中断 不支持中断锁获取 lockInterruptibly()
尝试获取 无超时尝试 API tryLock() / tryLock(timeout)
公平锁 无公平性配置 可选公平锁或非公平锁
条件队列 每个监视器一个等待集合 可以创建多个 Condition

默认优先使用 synchronized。只有确实需要可中断、超时、公平性或多条件队列时,再使用 ReentrantLock

底层实现

  • 同步代码块编译后使用 monitorentermonitorexit 字节码指令。
  • 同步方法通过方法的 ACC_SYNCHRONIZED 标志表示需要获取监视器。
  • 锁的具体状态和优化策略属于 JVM 实现细节,不应把某个 JDK 版本的“偏向锁—轻量级锁—重量级锁”实现当作 Java 语言规范。

易错点总结

  • 只看到 synchronized 就认为一定互斥,忽略了锁对象是否相同。
  • 用两个实例锁保护同一份静态变量,实际上无法互斥。
  • 锁住字符串常量、装箱对象等可能被共享的对象,导致无关代码发生锁竞争。
  • 在锁内执行耗时 I/O 或调用不受控的外部方法,使临界区过大。
  • 认为 volatile 可以代替 synchronized 保护复合操作。
  • 多把锁按不同顺序获取,可能造成死锁。

参考资料