你以为synchronized只是简单的加锁?Too young, too simple! 本文将带你深入多线程的星辰大海,揭秘高并发编程的底层奥秘
想象一下,你和你的情敌同时想追到女神。如果没有规则(锁),你们可能会打起来(数据错乱)。而锁策略,就是决定你们如何公平(或不公平)竞争女神的规则。
在多线程世界里,锁是保证线程安全、维护数据一致性的重要手段。但锁的实现方式多种多样,适用于不同的场景。本文将带你深入探讨常见的锁策略、CAS机制、synchronized原理、JUC常用类等高级主题。

锁类型 | 工作方式 | 适用场景 | 比喻 |
|---|---|---|---|
悲观锁 | 总是假设会冲突,先加锁再访问 | 冲突频繁的场景 | 约会前先问女神“有空吗?” |
乐观锁 | 假设不会冲突,直接访问,发现冲突再回滚 | 冲突较少的场景 | 直接去敲女神门,发现她忙就下次再来 |
synchronized初始使用乐观锁,竞争激烈时自动升级为悲观锁。
锁的核⼼特性"原子性",这样的机制追根溯源是CPU这样的硬件设备提供的.

类型 | 实现方式 | 性能开销 | 比喻 |
|---|---|---|---|
重量级锁 | 依赖操作系统mutex,涉及内核态切换 | 高 | 去银行柜台办业务,排队等待 |
轻量级锁 | 用户态完成,CAS实现 | 低 | 自助ATM机,自己操作 |
synchronized会从轻量级锁开始,竞争激烈时升级为重量级锁。
伪代码示例:
while (!tryLock(lock)) {
// 空循环,不断尝试
}比喻:
优缺点:
类型 | 规则 | 比喻 |
|---|---|---|
公平锁 | 先来后到,排队执行 | 先追女神的先上位 |
非公平锁 | 谁抢到是谁的 | 女神看谁顺眼选谁 |
synchronized是非公平锁。
问题: 同一个线程能否多次获取同一把锁?
示例:
synchronized void methodA() {
methodB(); // 递归或嵌套调用
}
synchronized void methodB() {
// 不会死锁
}
synchronized和ReentrantLock都是可重入锁。
适用场景:读多写少(如教务系统查看作业)
操作 | 读锁 | 写锁 |
|---|---|---|
读锁 | ✅ 不互斥 | ❌ 互斥 |
写锁 | ❌ 互斥 | ❌ 互斥 |
Java实现:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock(); // 加读锁
rwLock.writeLock().lock(); // 加写锁CAS = Compare And Swap,比较并交换。是一个硬件级原子操作。
伪代码:
boolean CAS(address, expectValue, swapValue) {
if (*address == expectValue) {
*address = swapValue;
return true;
}
return false;
}AtomicInteger atomicInt = new AtomicInteger(0);
atomicInt.getAndIncrement(); // 线程安全的i++内部实现:
public int getAndIncrement() {
int oldValue = value;
while (!CAS(value, oldValue, oldValue + 1)) {
oldValue = value;
}
return oldValue;
}问题: 值从A→B→A,CAS无法感知中间变化。
解决方案: 使用版本号(AtomicStampedReference)
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 1);
ref.compareAndSet(100, 50, 1, 2); // 期望值、新值、期望版本、新版本
无锁 → 偏向锁 → 轻量级锁(自旋) → 重量级锁比喻:
优化 | 说明 | 例子 |
|---|---|---|
锁消除 | JVM发现不可能竞争,直接去掉锁 | 单线程下的StringBuffer |
锁粗化 | 连续加锁解锁合并为一次大锁 | 多次打电话合并为一次交代所有任务 |
传统方式: 需要手动wait/notify,代码复杂
Callable方式:
Callable<Integer> callable = () -> {
int sum = 0;
for (int i = 1; i <= 1000; i++) sum += i;
return sum;
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
int result = futureTask.get(); // 阻塞等待结果比喻:
Callable = 麻辣烫订单FutureTask = 取餐小票ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 业务代码
} finally {
lock.unlock();
}与synchronized对比:
特性 | synchronized | ReentrantLock |
|---|---|---|
自动释放 | ✅ | ❌ 需手动unlock |
可中断 | ❌ | ✅ tryLock支持超时 |
公平锁 | ❌ | ✅ 可配置 |
条件变量 | ❌ | ✅ Condition精准唤醒 |
构造参数详解(公司模型):
参数 | 比喻 | 说明 |
|---|---|---|
corePoolSize | 正式工 | 核心线程数,永不辞退 |
maximumPoolSize | 正式工+临时工 | 最大线程数 |
keepAliveTime | 临时工空闲时间 | 超过则辞退 |
workQueue | 任务队列 | 存放待执行任务 |
handler | 拒单策略 | 任务太多时的处理方式 |
示例:
ExecutorService pool = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60, // 空闲时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new ThreadPoolExecutor.AbortPolicy()
);比喻: 停车场空位计数器
Semaphore semaphore = new Semaphore(4); // 4个车位
semaphore.acquire(); // P操作,申请资源
// 访问资源
semaphore.release(); // V操作,释放资源比喻: 跑步比赛,所有人到达终点才公布成绩
CountDownLatch latch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 完成任务
latch.countDown();
}).start();
}
latch.await(); // 等待所有任务完成
System.out.println("全部完成!");特性 | Hashtable | ConcurrentHashMap |
|---|---|---|
锁粒度 | 整个表一把锁 | 每个桶一把锁 |
性能 | 低 | 高 |
读写 | 全锁 | 读无锁,写锁桶 |
ConcurrentHashMap在JDK8中改为:数组 + 链表/红黑树 + CAS + synchronized
错误示例:
// 线程1
synchronized (lockA) {
synchronized (lockB) { ... }
}
// 线程2
synchronized (lockB) {
synchronized (lockA) { ... } // 可能死锁
}正确示例: 约定获取锁的顺序(如按lockA→lockB)
问题 | 答案要点 |
|---|---|
synchronized 可重入吗? | ✅ 是,递归不会死锁 |
CAS 的 ABA 问题如何解决? | 使用版本号(AtomicStampedReference) |
线程池参数含义? | 核心数、最大数、队列、拒绝策略 |
volatile 作用? | 保证可见性,禁止指令重排 |
死锁如何避免? | 锁排序、超时机制、避免嵌套锁 |
多线程编程是一门艺术,更是工程实践中的必备技能。从基础的synchronized到强大的JUC工具包,从锁策略到CAS无锁编程,每一个知识点都是为了在安全与性能之间找到最佳平衡点。
记住: 没有最好的锁,只有最合适的锁。理解原理,结合实际场景,才能写出高效稳定的并发程序。
参考资料: