用通俗语言讲透 Java 锁:synchronized 原理与锁升级

如果你把 Java 程序当成一个 “共享办公室”,那多线程就像同时在办公室里工作的员工。有些资源是大家共用的,比如打印机、咖啡机 —— 这对应程序里的 “共享变量”。要是两个员工同时抢着用打印机,很可能会把文件打混;同理,多线程同时操作共享变量,也会出现数据错误。这时候就需要一把 “锁”,让大家按顺序使用资源,而synchronized就是 Java 里最常用的 “锁钥匙”。

一、先搞懂:synchronized 到底是啥?

synchronized翻译成中文是 “同步”,但你不用记这个术语,只要记住它的核心作用:让多个线程 “排队” 访问共享资源,避免互相干扰

比如办公室里只有一台咖啡机,员工 A 正在接咖啡时,员工 B 只能等着 —— 这就是synchronized的效果。它就像咖啡机上的 “使用中” 牌子,谁拿到牌子谁能用,用完再把牌子交给下一个人。

在 Java 里,synchronized能锁两样东西:

  1. 锁方法:比如public synchronized void makeCoffee(),这时候 “锁” 绑在方法所在的对象上(相当于把 “使用中” 牌子挂在咖啡机本身);

  2. 锁代码块:比如synchronized (this) { ... },这里的this就是 “锁对象”(相当于指定某一个东西当 “牌子”,比如咖啡机旁边的桌子)。

二、底层原理:为啥 synchronized 能 “锁住” 资源?

你可能会好奇:这把 “锁” 到底是怎么实现的?其实答案藏在 Java 对象的 “身份证” 里 ——对象头(Object Header)

每个 Java 对象在内存里都有个 “头信息”,就像每个人的身份证包含姓名、性别一样,对象头里也存着关键信息,其中就有 “锁状态”。比如:

  • 这个对象有没有被加锁?

  • 哪个线程正在用这个锁?

当你用synchronized给对象加锁时,JVM(Java 的 “管家”)会修改这个对象头里的 “锁状态”,相当于在身份证上盖个 “已占用” 的章。其他线程想拿这个锁时,一看章就知道要排队等。

三、重点:锁为啥要 “升级”?(从无锁到重量级锁)

你可能会想:直接搞一把 “最强的锁” 不就行了?为啥还要升级?

这就像开车:平时在小区里挪车,不用开太快,也不用复杂操作;但上了高速,就得踩油门、注意限速。如果不管场景都用 “高速模式”,反而费油又麻烦。

Java 的锁也是一样 ——不同场景用不同强度的锁,才能兼顾 “速度” 和 “安全”。如果不管线程多不多,都用最强的锁,程序会变慢(就像堵车时还开高速的车,灵活度不够)。

所以 JVM 会根据 “线程竞争的激烈程度”,把锁从弱到强一步步升级,总共分 4 个阶段:

1. 第一阶段:无锁状态(没人抢资源)

这是最轻松的状态 —— 共享资源没人用,所以不用加锁。

比如办公室的咖啡机刚买回来,还没人用,这时候谁想接咖啡都能直接用,不用排队。程序里的对象默认就是无锁状态,对象头里也没盖任何 “章”。

2. 第二阶段:偏向锁(只有一个线程抢)

如果只有一个线程一直用这个资源,就不用搞复杂的锁 —— 给这个线程 “开绿灯”,让它直接用,这就是偏向锁。

比如每天只有员工 A 用咖啡机,办公室管家(JVM)就会在咖啡机的 “身份证” 上写:“专属 A 使用”。A 每次来用,不用排队,直接拿 —— 这样既安全(没人抢),又快(不用查排队名单)。

原理:JVM 会在对象头里记录 “当前持有锁的线程 ID”。这个线程下次再想拿锁时,一看 ID 是自己的,直接就过,不用做额外检查。

3. 第三阶段:轻量级锁(两个线程偶尔抢)

如果突然来了第二个线程(比如员工 B)也想用咖啡机,这时候偏向锁就不够用了(不能只偏向 A 了),但因为只是 “偶尔抢”(比如 A 用的时候 B 刚好来,A 用完 B 再用,不是同时抢),所以不用搞太重的锁,用 “轻量级锁” 就行。

这就像 A 和 B 偶尔先后用咖啡机:管家不用搞正式的排队名单,只要让他们 “打个招呼”——A 用完了告诉 B“我用完了,你来吧”,B 用完再告诉 A(如果 A 还来的话)。不用专门派个人盯着排队,效率很高。

原理:这时候 JVM 会把对象头里的 “偏向锁标记” 去掉,改成 “轻量级锁标记”,并且让两个线程通过 “CAS”(一种快速的 “对比 - 修改” 操作)来抢锁。简单说就是:线程先看锁是不是空闲的,如果是,就 “抢着” 把自己的线程 ID 写进对象头,谁先写上谁用。

注意:轻量级锁只适合 “线程交替使用资源” 的场景 —— 如果两个线程同时抢(比如 A 刚接一半,B 就过来抢),轻量级锁就扛不住了,得升级成重量级锁。

4. 第四阶段:重量级锁(多个线程疯狂抢)

如果来了很多线程(比如早高峰,10 个员工都要抢咖啡机),大家同时围上来,这时候 “打招呼” 不管用了,必须搞正式的排队 —— 这就是重量级锁。

就像超市排队结账:得有个收银员(相当于操作系统的 “线程调度器”)专门管排队,想结账的人(线程)都站在队伍里,收银员喊谁谁才能上。这样虽然慢(要等排队),但绝对安全,不会乱。

原理:到了重量级锁,JVM 就不再自己管锁了,而是 “求助” 操作系统 —— 让操作系统来管理线程的排队和唤醒。因为操作系统的线程调度是 “重量级” 的(要切换线程状态,消耗资源多),所以这个锁才叫 “重量级锁”。

这时候对象头里会记录 “指向重量级锁的指针”,线程抢不到锁时,会被操作系统 “挂起”(相当于站到排队队伍里),等锁空闲了再被 “唤醒”(收银员喊下一个)。

更多推荐