文章目录
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
零、引入
王二的指尖在键盘上悬着,迟迟不敢按下运行键。桌上的绿萝垂下来的藤蔓扫过手背,痒痒的,却没让他分神 —— 他用 synchronized 解决了并发安全问题,但在 1000 个线程的高并发测试中,程序的响应时间从 10ms 飙到了 200ms,日志里满是 “线程阻塞” 的痕迹。
“不是说 synchronized 优化过了吗?怎么高并发下还是这么慢?” 他皱着眉,把桌上的便签纸翻得哗哗响,那上面记着哇哥上次讲的 synchronized 基础原理,却找不到解决高并发慢的答案。
哇哥端着一杯温热的蜂蜜水走过来,把杯子放在王二手边:“你以为 synchronized 只有一种样子?它就像一个慢慢长大的人,小时候(低并发)青涩又灵活,长大后(高并发)沉稳又可靠。锁升级就是它的成长之旅 —— 从偏向锁到轻量级锁,再到重量级锁,它会根据并发情况调整自己的守护方式。”
点赞 + 关注,跟着哇哥走进 synchronized 的成长之旅,把锁升级的秘密扒得明明白白,下次高并发场景用 synchronized,也能让程序跑得又稳又快。

一、王二的 “高并发卡顿代码”:不懂锁升级的坑
王二写的高并发计数代码,用 synchronized 修饰了核心方法,低并发时没问题,高并发下却卡顿严重。代码如下:
package cn.tcmeta.synchronizeds;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author: laoren
* @description: 王二的坑:高并发下用synchronized,却不知道锁升级导致卡顿
* @version: 1.0.0
*/
public class SynchronizedHighConcurrencySample {
private static int count = 0;
// 高并发:1000个线程
private static final int THREAD_COUNT = 1000;
// 每个线程计数100次
private static final int LOOP_COUNT = 100;
// synchronized修饰静态方法,锁是Class对象
private static synchronized void increment() {
count++;
}
static void main() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
long start = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.submit(() -> {
try {
for (int j = 0; j < LOOP_COUNT; j++) {
increment();
}
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
long end = System.currentTimeMillis();
System.out.println("最终计数:" + count);
System.out.println("理论计数:" + THREAD_COUNT * LOOP_COUNT);
System.out.println("高并发耗时:" + (end - start) + "ms");
}
}

王二叹了口气:“计数是准了,但这速度也太慢了,用户肯定受不了。”
哇哥拍了拍他的肩膀,语气温柔:“这不是 synchronized 不行,是你没懂它的成长规律。低并发时,它是‘偏向锁’,像个认人的小管理员,只要是同一个线程来,直接放行,不用费劲验证;当有少量线程竞争时,它变成‘轻量级锁’,用 CAS 和线程自旋来竞争锁,不用阻塞线程;只有高并发、竞争激烈时,它才会变成‘重量级锁’,让线程阻塞等待 —— 你这 1000 个线程同时抢锁,它直接长成了最沉稳的样子,自然会慢一点,但这是最稳妥的守护方式。”
二、用 “成长之旅” 讲透锁升级:从青涩到成熟

哇哥拉过一把椅子坐下,从包里掏出一本旧笔记本,翻开空白页,画了一条成长曲线 —— 这是他讲解进阶技术的习惯,用成长的故事把复杂的原理讲得亲切。
“你看这条曲线,” 他指着笔记本,“synchronized 的锁升级,就像一个人的成长:
- 婴儿期(无锁):刚创建的对象,还没有锁,就像刚出生的宝宝,还不需要承担守护责任;
- 幼儿期(偏向锁):如果只有一个线程来访问,它就会‘偏向’这个线程,把线程 ID 存在对象头里。下次这个线程再来,直接认 ID 放行,不用任何竞争 —— 就像幼儿只认自己的爸爸妈妈,别人不来抢,就安安稳稳的;
- 少年期(轻量级锁):有少量线程来竞争了,它不会直接让线程阻塞,而是用 CAS 指令让线程‘自旋’(循环等待),尝试获取锁 —— 就像少年遇到小争执,不会哭闹,而是试着自己解决;
- 成年期(重量级锁):竞争太激烈了,自旋也没用,它就会调用操作系统的 mutex(互斥量),让没抢到锁的线程阻塞等待 —— 就像成年人遇到大事,会找专业的人帮忙,虽然麻烦一点,但能保证秩序。”
他顿了顿,补充道:“锁升级是不可逆的,就像人不能返老还童。一旦升级成重量级锁,就再也回不到偏向锁或轻量级锁了。”

👉 锁升级核心拆解(王二记在笔记本上)
王二拿出笔,跟着哇哥的话,认真地记在笔记本上,字里行间都是对技术的敬畏:
✅ 各阶段锁的核心特点

💯 锁升级的触发条件
- 偏向锁→轻量级锁:有其他线程尝试获取偏向锁,且当前偏向线程不是它;
- 轻量级锁→重量级锁:自旋次数达到阈值(JDK 默认 10 次),或者自旋的线程数超过 CPU 核心数的一半。
锁升级的底层依赖:对象头的变化
- 无锁:对象头的 mark word(标记字)存储对象的哈希码、GC 年龄等信息;
- 偏向锁:mark word 存储偏向线程 ID、偏向标志位(1);
- 轻量级锁:线程创建锁记录(Lock Record),用 CAS 把 mark word 的锁记录指针指向自己;
- 重量级锁:mark word 存储指向监视器锁(monitor)的指针,监视器锁管理阻塞线程队列。
三、代码示例:验证锁升级,优化高并发性能

哇哥拿过王二的鼠标,写了两个示例:一个验证锁升级的存在,一个优化高并发下的 synchronized 性能。
🔥 示例 1:验证锁升级的存在(通过打印对象头)
package cn.tcmeta.synchronizeds;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
/**
* @author: laoren
* @description: 验证synchronized的锁升级(使用JOL工具安全获取对象头信息)
* @version: 1.0.0
*/
public class LockUpgradeVerification {
public static void main(String[] args) throws InterruptedException {
// 初始化JOL VM信息
VM.current().details();
Object lock = new Object();
System.out.println("无锁状态对象头信息:");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("偏向锁状态对象头信息:");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
try {
Thread.sleep(1000); // 模拟持有锁一段时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Thread-1 被中断");
}
}
}, "Thread-1");
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(500); // 让thread1先获得锁
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Thread-2 初始化阶段被中断");
return;
}
synchronized (lock) {
System.out.println("竞争后锁状态对象头信息:");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
}
}, "Thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
执行结果:
无锁状态对象头信息:
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x001831d0
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
偏向锁状态对象头信息:
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000000 (thin lock: 0x0000000000000000)
8 4 (object header: class) 0x001831d0
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
竞争后锁状态对象头信息:
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000001efc050f872 (fat lock: 0x000001efc050f872)
8 4 (object header: class) 0x001831d0
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
👉 示例 2:优化高并发下的 synchronized 性能
package cn.tcmeta.synchronizeds;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder;
/**
* @author: laoren
* @description: 优化思路:细粒度锁+减少锁竞争,或用原子类替代
* @version: 1.0.0
*/
public class SynchronizedHighConcurrencyOpt {
// 优化1:细粒度锁,每个分区一个锁
private static final int PARTITION_COUNT = 10;
private static final int[] counts = new int[PARTITION_COUNT];
private static final Object[] locks = new Object[PARTITION_COUNT];
static {
// 初始化每个分区的锁
for (int i = 0; i < PARTITION_COUNT; i++) {
locks[i] = new Object();
}
}
// 按线程ID取模分配分区,减少锁竞争
private static void incrementByPartition() {
long threadId = Thread.currentThread().getId();
int partition = (int) (threadId % PARTITION_COUNT);
synchronized (locks[partition]) {
counts[partition]++;
}
}
static void main() throws InterruptedException {
int THREAD_COUNT = 1000;
int LOOP_COUNT = 100;
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
// 测试优化后的细粒度锁
long start1 = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.submit(() -> {
try {
for (int j = 0; j < LOOP_COUNT; j++) {
incrementByPartition();
}
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
long end1 = System.currentTimeMillis();
// 统计总计数
int total = 0;
for (int count : counts) {
total += count;
}
System.out.println("细粒度锁-最终计数:" + total);
System.out.println("细粒度锁-耗时:" + (end1 - start1) + "ms");
// 测试用原子类替代(高并发计数最优解)
LongAdder longAdder = new LongAdder();
CountDownLatch countDownLatch2 = new CountDownLatch(THREAD_COUNT);
long start2 = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.submit(() -> {
try {
for (int j = 0; j < LOOP_COUNT; j++) {
longAdder.increment();
}
} finally {
countDownLatch2.countDown();
}
});
}
countDownLatch2.await();
long end2 = System.currentTimeMillis();
executorService.shutdown();
System.out.println("LongAdder-最终计数:" + longAdder.sum());
System.out.println("LongAdder-耗时:" + (end2 - start2) + "ms");
}
}

王二眼睛瞪得圆圆的:“居然快了这么多!原来不是 synchronized 不行,是我用的方式不对。”
“是啊,” 哇哥笑了笑,“锁升级是 synchronized 的成长,我们用它的时候,也要根据它的成长规律来。高并发下,要么用细粒度锁减少竞争,不让它升级成重量级锁;要么用原子类替代,这都是更灵活的选择。”
四、面试必问:synchronized 进阶核心题(附答案)

✔️ 面试题 1:synchronized 的锁升级过程是什么?各阶段锁的适用场景是什么?
用 “成长之旅” 的例子答,面试官一听就懂:
- 锁升级过程:无锁→偏向锁→轻量级锁→重量级锁(不可逆);
- 各阶段适用场景:
- 偏向锁:单线程访问,无竞争(比如单线程循环调用加锁方法);
- 轻量级锁:少量线程竞争,自旋能获取到锁(比如 10 个线程竞争);
- 重量级锁:高并发激烈竞争,自旋失效(比如 1000 个线程竞争)。
📢 面试题 2:偏向锁的核心作用是什么?为什么要设计偏向锁?
- 核心作用:减少单线程访问时的锁开销;
- 设计原因:大多数场景下,锁都是被同一个线程多次访问的,没有竞争。如果每次都用 CAS 或阻塞,会有不必要的开销;
- 实现原理:把访问锁的线程 ID 存在对象头的 mark word 里,下次该线程访问时,直接匹配线程 ID 即可获取锁,不用任何竞争操作,几乎无开销。
🔔 面试题 3:高并发场景下,如何优化 synchronized 的性能?
- 减小锁粒度:把大的临界区拆分成小的,用多个锁保护不同的资源(比如示例中的分区锁),减少锁竞争;
- 避免锁升级:尽量让锁保持在偏向锁或轻量级锁状态,减少高并发竞争;
- 用原子类替代:简单的计数 / 累加场景,用 LongAdder、AtomicInteger 等原子类替代 synchronized,无锁更高效;
- 合理设置线程池大小:避免线程数过多导致锁竞争激烈,线程池大小建议和 CPU 核心数匹配或略多。
五、总结:synchronized 进阶核心心法(王二编的顺口溜)
王二把进阶知识点编成了顺口的小句子,贴在笔记本封面,方便随时看:
- 锁升级不可逆,无锁偏向轻量重;
- 单线程用偏向,少量竞争轻量中;
- 高并发变重量,阻塞等待保秩序;
- 优化性能有妙招,细粒度锁减竞争;
- 简单计数原子类,无锁高效更轻松。
王二用细粒度锁优化了项目里的高并发代码,测试后响应时间从 200ms 降到了 30ms,领导看了测试报告,笑着说:“这代码写得越来越有章法了,能根据场景选对技术,才是真的懂了。”
王二拿着报告去找哇哥,脸上满是成就感。哇哥喝了一口蜂蜜水,慢悠悠说出那句收尾的话:
哇哥说:“锁的升级,就像人的成长,从青涩到成熟,都是为了更好地适应环境。synchronized 的温柔,就在于它从不会一开始就摆出强硬的姿态,而是根据实际情况调整自己的守护方式。这世上本没有一成不变的技术,只有懂得灵活应变的开发者。”




被折叠的 条评论
为什么被折叠?



