连载中 3/21

synchronized(上):它锁的不是代码,是对象

2026-10-24 · 72 阅读 · 0 评论 · 0 赞

一行 synchronized,超卖归零

第 1 篇的超卖 Demo,最小修复只加一个词:把扣库存的方法用 synchronized 修饰,1000 件卖出正好 1000 件。但先回答更重要的问题——synchronized 到底锁住了什么?很多人以为它锁的是方法、是那段代码。错。它锁的是对象,代码只是被这把对象锁圈起来的「排队进场区域」。

三种用法,三把不同的锁

synchronized 有三种用法,锁的对象完全不同,混着用就是三个互不干扰的队列:

class Counter {
    private int count;

    // 用法一:修饰实例方法 → 锁 this
    public synchronized void inc() { count++; }

    // 用法二:修饰静态方法 → 锁 Counter.class(类对象,全局唯一)
    public static synchronized void staticInc() { }

    // 用法三:修饰代码块 → 锁你指定的那个对象
    private final Object lock = new Object();
    public void dec() {
        synchronized (lock) { count--; }
    }
}

用法三最值得玩味——任何对象都能当锁,因为每个 Java 对象出厂自带一把锁。这也解释了最经典的翻车:两个线程各自 new 了 Counter,各自调 synchronized 实例方法——锁的是两个不同的 this,互不干扰,该超卖照旧超卖。要互斥,必须抢同一把锁。

锁的信息藏在哪:对象头 Mark Word

对象在堆里除了字段数据还有一段对象头,其中 Mark Word 是锁的主战场:无锁、偏向、轻量级、重量级四种锁状态,全靠它里面几个比特位的组合来标记,哈希码、GC 分代年龄也挤在这里面。所以给对象「上锁」不需要额外的锁对象——锁状态就记在对象自己头上。这也为下一篇的锁升级铺了路:锁为什么能升级?因为 Mark Word 的位布局可以按需切换。

字节码里的两条实现路径

javap -v 反编译看真相:同步代码块靠一对 monitorenter / monitorexit 指令包住——enter 时 monitor 计数加一(若发现计数者就是自己则直接加一,这叫可重入,同一线程反复进同一把锁不会卡死自己),exit 时减一,减到零才真正放锁。同步方法则没有这对指令,只有方法 flags 里多一个 ACC_SYNCHRONIZED 标志,JVM 调用时看到它就自动完成加解锁。两条路径殊途同归,背后都是同一个 monitor:

$ javap -v Counter

public synchronized void inc();
  flags: ACC_PUBLIC, ACC_SYNCHRONIZED   // 同步方法:标志位实现

public void dec();
    ...
    monitorenter                         // 同步代码块:指令对实现
    ...
    monitorexit                          // 注意:会有两个 monitorexit
    ...                                  // 一个走正常出口,一个走异常出口

留意那个细节:编译器会生成两个 monitorexit,一个是正常出口,一个挂在异常路径上——保证代码就算抛异常,锁也一定被释放。synchronized 从语法层面杜绝了「忘了解锁」,这正是它比手动 Lock 省心的地方。

等锁的线程去哪了

呼应第 2 篇的状态图:拿不到锁的线程进 BLOCKED,挂在锁对象的等待队列里,锁释放时 JVM 唤醒一个接着抢。这套排队不保证公平——刚释放锁的线程可能立刻又抢回去,排了很久的队也可能一直抢不到。非公平是 synchronized 的默认性格,也是第 11 篇 ReentrantLock 专门提供公平选项的动机。

骨架讲完。下一篇拆 synchronized 最玄学的部分:明明是同一把锁,为什么有四种形态、还会自动升级?以及让无数人栽跟头的 wait / notify——为什么必须在 synchronized 块里调用,为什么等待条件必须写在 while 里。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#Java#synchronized#锁#JUC#并发编程

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10113 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9876 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9306 阅读 · 21 评论 · 287 赞