同步实例方法锁 this,保护实例字段;静态同步方法锁 Class 对象,保护静态字段;混用会导致锁失效,必须统一锁对象以确保线程安全。

同步实例方法:锁住的是 this 对象
当你在非静态方法前加 synchronized,JVM 会自动以当前实例(即 this)为锁对象。这意味着:同一对象的多个线程调用该方法时会互斥;但不同对象的线程可以并行执行——它们锁的不是同一个东西。
常见错误现象:i++ 这类非原子操作在未加锁时结果随机,比如两个线程各执行 100 万次 increase(),最终值远小于 200 万。
- 必须确保所有并发线程操作的是**同一个实例对象**,否则锁失效(如每个线程 new 一个新对象)
- 锁粒度是整个方法体,若方法内含大量非临界逻辑(如日志、IO),会拖慢吞吐
- 不能靠它保护静态字段,因为
this锁不作用于类级别资源
同步静态方法:锁住的是 MyClass.class
public static synchronized void doSomething() 实际等价于 synchronized(MyClass.class) { ... }。它锁定的是类的 Class 对象,因此对所有该类的实例都生效,适合保护静态共享状态。
使用场景:计数器、单例初始化、配置加载等需要全局唯一控制点的地方。
- 静态同步方法和实例同步方法互不干扰——一个线程持
MyClass.class锁,不影响另一个线程进入该类某实例的synchronized实例方法 - 若同时存在静态字段和实例字段的读写,只加静态同步可能漏掉实例级竞争,得分开考虑
- 注意类加载器隔离问题:相同类名但由不同 ClassLoader 加载的类,其
Class对象不相等,锁也不互通
为什么不能混用 this 锁和 class 锁来保护同一份数据
假设你有一个静态变量 counter,又在实例方法里用 synchronized(this) 去修改它——这是危险的。因为每个实例的 this 锁彼此独立,10 个线程各持一个实例锁,都能同时进入临界区,counter 依然会出错。
真正起作用的,永远是「谁在锁、锁什么」这个组合。
- 保护实例字段 → 用实例锁(
synchronized实例方法 或synchronized(this)) - 保护静态字段 → 用类锁(
synchronized静态方法 或synchronized(MyClass.class)) - 混合访问(如静态 + 实例字段共用逻辑)→ 推荐统一用类锁,或拆成更细粒度的同步块,避免误判锁范围
容易被忽略的重入与异常场景
synchronized 是可重入锁,同一线程再次进入自己已持有的锁不会死锁。但很多人忘了:锁是自动释放的,前提是方法正常返回或抛出异常后退出作用域。
如果临界区内发生未捕获异常,锁仍会被释放——这点和 ReentrantLock 的显式 unlock() 不同,是安全的,但也意味着你无法靠“没释放锁”来感知崩溃。
- 不要在
synchronized方法里长期阻塞(如无限等待 IO),否则其他线程会无限期挂起 - 避免在同步块内调用外部不可控方法(如回调、第三方库),以防对方反过来调用本类同步方法,引发死锁
- 监控锁竞争最直接的方式是
jstack查看线程堆栈中是否有大量WAITING on java.lang.Object@xxx
真正的难点不在语法,而在于判断「哪段代码是临界区」「该用哪个对象当锁」「有没有隐式共享状态被漏掉」。写完之后,不妨问一句:如果我把这个方法改成非同步,哪些变量/行为会变?答案就是你该锁住的东西。










