synchronized关键字是做什么用的_Java同步机制详解

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

synchronized 的核心作用是:让多线程「排队访问共享资源」,避免数据错乱、读到脏值或执行结果不可预期。

为什么必须用 synchronized 而不是靠自己加 flag?

boolean isRunning 这类手动标记根本不可靠——它既不能阻止线程同时进入临界区(缺乏互斥),也无法保证修改立刻被其他线程看到(缺乏可见性)。而 synchronized 一次性解决三个问题:

  • 原子性:同一时刻只允许一个线程执行被锁住的代码块
  • 可见性:退出同步块时,所有变量修改强制刷回主内存;进入时清空本地缓存,重新从主内存加载
  • 有序性:隐式插入内存屏障,禁止编译器和 CPU 对临界区内的读写重排序

这三点不是“可选功能”,是 JVM 规范强制保证的,靠手写逻辑无法等效实现。

synchronized(this) 和 synchronized(ClassName.class) 到底锁谁?

锁对象决定了同步范围,选错就等于没锁:

  • 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
  • 两者卡住,互相等待对方释放锁

规避方法只有两条硬规则:

  • 所有线程必须以**相同顺序**获取多个锁(比如永远先 lock1lock2
  • 尽量用「一个锁」解决问题;实在需要多个锁,优先考虑 ReentrantLock.tryLock(timeout) 带超时的尝试获取,失败就释放已持锁并重试

性能真那么差?JDK 1.

6 之后的优化你得知道

很多人还在用“synchronized 是重量级锁”当口头禅,但现实是:

  • 偏向锁(默认开启):无竞争时几乎零成本,只在对象头 mark word 记个线程 ID
  • 轻量级锁:竞争不激烈时用 CAS 替代系统互斥量,避免进内核态
  • 锁膨胀是逐级触发的——只有真正高并发争抢才会升级为重量级锁
  • 除非你压测发现 synchronized 成为瓶颈,否则别急着换 ReentrantLock;后者更灵活,但也更易出错(比如忘记 unlock()

真正影响性能的,往往不是锁本身,而是同步块里干了太多事——比如在 synchronized 里调远程接口、解析大 JSON、做复杂计算。把耗时操作移出去,比换锁类型管用十倍。

最常被忽略的一点:synchronized 的锁释放是「自动且绝对可靠」的——哪怕方法抛异常、return 中断、甚至 JVM crash(在正常运行路径下),JVM 都会确保 monitorexit 执行。这点比手动管理的锁更安全,也更省心。