synchronized核心作用是让多线程排队访问共享资源,解决原子性、可见性、有序性三大问题;锁对象决定同步范围,需避免死锁与滥用;JDK 1.6后已优化,性能并非瓶颈。

synchronized 的核心作用是:让多线程「排队访问共享资源」,避免数据错乱、读到脏值或执行结果不可预期。
为什么必须用 synchronized 而不是靠自己加 flag?
靠 boolean isRunning 这类手动标记根本不可靠——它既不能阻止线程同时进入临界区(缺乏互斥),也无法保证修改立刻被其他线程看到(缺乏可见性)。而 synchronized 一次性解决三个问题:
- 原子性:同一时刻只允许一个线程执行被锁住的代码块
- 可见性:退出同步块时,所有变量修改强制刷回主内存;进入时清空本地缓存,重新从主内存加载
- 有序性:隐式插入内存屏障,禁止编译器和 CPU 对临界区内的读写重排序
这三点不是“可选功能”,是 JVM 规范强制保证的,靠手写逻辑无法等效实现。
synchronized(this) 和 synchronized(ClassName.class) 到底锁谁?
锁对象决定了同步范围,选错就等于没锁:
立即学习“Java免费学习笔记(深入)”;
-
synchronized(this)锁的是当前实例对象,不同对象之间完全不干扰。适合保护实例变量(如private int count) -
synchronized(Counter.class)或public static synchronized void inc()锁的是类对象,所有该类的实例共用一把锁。适合保护静态变量或全局计数器 - 绝不能写
synchronized(new Object())—— 每次新建对象,锁对象都不同,等于没加锁
public class Counter {
private int instanceCount = 0;
private static int staticCount = 0;
// ✅ 正确:保护实例变量
public void incrementInstance() {
synchronized (this) {
instanceCount++;
}
}
// ✅ 正确:保护静态变量
public static void incrementStatic() {
synchronized (Counter.class) {
staticCount++;
}
}
}
常见死锁场景和怎么绕开?
死锁不是小概率事件,而是锁顺序不一致的必然结果。典型模式:
- 线程 A 先锁
lock1再锁lock2 - 线程 B 先锁
lock2再锁lock1 - 两者卡住,互相等待对方释放锁
规避方法只有两条硬规则:
- 所有线程必须以**相同顺序**获取多个锁(比如永远先
lock1后lock2) - 尽量用「一个锁」解决问题;实在需要多个锁,优先考虑
ReentrantLock.tryLock(timeout)带超时的尝试获取,失败就释放已持锁并重试
性能真那么差?JDK 1.6 之后的优化你得知道
很多人还在用“synchronized 是重量级锁”当口头禅,但现实是:
- 偏向锁(默认开启):无竞争时几乎零成本,只在对象头 mark word 记个线程 ID
- 轻量级锁:竞争不激烈时用 CAS 替代系统互斥量,避免进内核态
- 锁膨胀是逐级触发的——只有真正高并发争抢才会升级为重量级锁
- 除非你压测发现
synchronized成为瓶颈,否则别急着换ReentrantLock;后者更灵活,但也更易出错(比如忘记unlock())
真正影响性能的,往往不是锁本身,而是同步块里干了太多事——比如在 synchronized 里调远程接口、解析大 JSON、做复杂计算。把耗时操作移出去,比换锁类型管用十倍。
最常被忽略的一点:synchronized 的锁释放是「自动且绝对可靠」的——哪怕方法抛异常、return 中断、甚至 JVM crash(在正常运行路径下),JVM 都会确保 monitorexit 执行。这点比手动管理的锁更安全,也更省心。










