synchronized本质是对对象加锁:实例方法锁this,静态方法锁class对象;线程安全取决于锁对象是否一致及覆盖所有共享路径,需依状态共享范围合理选择实例锁或类锁。

怎么用 synchronized 锁住一个对象
直接在方法或代码块上加 synchronized,本质都是对某个对象加锁。普通实例方法加 synchronized,锁的是当前实例(this);静态方法加 synchronized,锁的是当前类的 Class 对象。
常见误用是以为「加了 synchronized 就线程安全」,其实关键看锁的对象是否一致、是否覆盖所有共享访问路径。
- 多个线程调用同一个对象的
synchronized实例方法 → 串行执行 - 多个线程调用不同对象的相同
synchronized实例方法 → 不互斥(锁对象不同) - 一个线程在
synchronized(this)块里,另一个调用该对象的synchronized方法 → 互斥(锁同一对象) - 静态方法和
synchronized(YourClass.class)等价,但和实例锁完全无关
synchronized(this) 和 synchronized(staticObject) 的区别
前者锁实例,后者锁类级别资源。如果要保护全局计数器、缓存、配置加载等跨实例共享的状态,必须用类锁,否则每个实例都有一把独立锁,起不到同步作用。
典型陷阱:在单例模式中误用 this 锁,而单例本身可能被反射/反序列化绕过,导致锁失效;更稳妥的是显式用 private static final Object LOCK = new Object()。
立即学习“Java免费学习笔记(深入)”;
-
synchronized(this):适合保护当前对象内部状态(如ArrayList的 size、elementData) -
synchronized(YourClass.class):适合初始化逻辑(如双重检查单例里的instance == null判断) - 避免用字符串字面量或外部传入对象作锁(如
synchronized("LOCK")),容易因字符串常量池或引用混用导致意外锁竞争
为什么 synchronized 不会死锁但可能饥饿
Java 的 synchronized 是可重入锁,同一线程可多次进入同一把锁,且 JVM 保证解锁顺序与加锁顺序相反。但它不提供公平性控制——默认非公平,先到不一定先得,高并发下可能某些线程长期拿不到锁(饥饿)。
- 不会因为「自己锁自己」死锁(可重入性保障)
- 但嵌套锁顺序不一致会真死锁:
thread1先锁 A 再锁 B,thread2先锁 B 再锁 A → 必须统一加锁顺序 - 没有超时机制:
synchronized无法像Lock.tryLock(long, TimeUnit)那样主动放弃,阻塞就是无限期等待 - JVM 层面对锁做了优化(偏向锁→轻量级锁→重量级锁),但 JDK 15+ 已默认禁用偏向锁,实际多为轻量级锁(CAS + 自旋)或系统互斥量
底层到底发生了什么(HotSpot 简化视角)
每个 Java 对象头(Object Header)里有 Mark Word,synchronized 的加锁/解锁操作,本质是通过 CAS 修改 Mark Word 中的锁标志位和指向 Monitor 的指针。Monitor 是 C++ 实现的,包含 Owner、EntryList、WaitSet 等字段。
当锁竞争激烈时,JVM 会将轻量级锁膨胀为重量级锁,此时线程会挂起(park),由操作系统调度唤醒(unpark)。这个过程涉及用户态/内核态切换,开销远大于自旋。
- 无竞争:偏向锁(已弃用)或轻量级锁(CAS 替换 Mark Word)→ 几乎无额外开销
- 低竞争:自旋等待(spin)→ 节省上下文切换,但浪费 CPU
- 高竞争:膨胀为重量级锁 → 线程进
EntryList阻塞,唤醒依赖 OS - 调用
wait()会释放锁并进入WaitSet;notify()只是将其移回EntryList,不立即获得锁
public class Counter {
private int count = 0;
private static final Object CLASS_LOCK = new Object();
public void increment() {
synchronized (this) { // 锁实例
count++;
}
}
public static void resetAll() {
synchronized (CLASS_LOCK) { // 锁类级别资源
// 清空所有实例共用的缓存等
}
}
}
真正难的不是语法,是判断「哪段状态需要保护」「锁的粒度是否合理」「有没有隐式依赖其他锁」。比如在 synchronized 块里调用外部回调函数,就可能把锁的范围意外扩大,甚至引发死锁。










