将用一家"线程茶铺"的故事,带你彻底理解Java两大锁机制的区别。这家店有两位店长:Synchronized店长和ReentrantLock店长,他们的管理风格截然不同!
🏪 故事背景:线程茶铺的日常
想象一家奶茶店:
- 顾客 = 线程(Thread)
- 制作台 = 共享资源(临界区)
- 排队规则 = 锁机制
两位店长用不同方式管理制作台的使用权:
🧙 第一章:Synchronized店长(synchronized关键字)
📜 管理特点
- 自动门禁系统:顾客进入制作区自动上锁,离开自动解锁
- 先到先服务:但允许新顾客插队(非公平锁)
- 单一等待区:所有等待顾客都在一个休息区
🧾 工作规则(源码原理)
java
public class SynchronizedTeaShop {
private int teaCount = 0; // 奶茶库存
// 同步方法:自动加锁解锁
public synchronized void makeTea(String customer) {
System.out.println(customer + "开始制作奶茶");
teaCount++;
// 模拟制作时间
try { Thread.sleep(100); } catch (Exception e) {}
System.out.println(customer + "完成!库存:" + teaCount);
}
// 同步代码块:更灵活控制
public void buyTea(String customer) {
System.out.println(customer + "进入店铺");
// 只锁制作台区域
synchronized(this) {
System.out.println(customer + "拿到制作台使用权");
makeTea(customer);
}
System.out.println(customer + "离开店铺");
}
}
⚙️ 底层机制(JVM实现)
java
// 伪代码展示synchronized原理
void enterSynchronized(Object monitor) {
if (尝试获取轻量级锁成功) return; // 无竞争场景
// 竞争场景升级为重量级锁
Monitor mon = getMonitor(monitor);
mon.lock();
while (!CAS(mon.owner, null, currentThread)) {
// 获取失败进入等待队列
mon.wait();
}
}
void exitSynchronized(Object monitor) {
Monitor mon = getMonitor(monitor);
mon.unlock();
// 唤醒等待队列中的线程
notifyWaiters(mon);
}
✅ Synchronized店长的优势
❌ 局限性
- 单一等待条件:所有等待线程只能在一个休息区
- 不可中断等待:线程等待时无法响应中断
- 非公平锁:新线程可能插队
🧑💼 第二章:ReentrantLock店长(ReentrantLock类)
📜 管理特点
- 手动门禁卡:顾客需要刷卡进入,刷卡离开
- 公平模式可选:可配置严格排队或允许插队
- 多等待区:可创建VIP等待区、普通等待区等
🧾 工作规则(源码原理)
java
import java.util.concurrent.locks.*;
public class ReentrantLockTeaShop {
private int teaCount = 0;
// 核心:手动锁对象
private final ReentrantLock lock = new ReentrantLock(true); // true=公平模式
// 多条件等待区
private final Condition vipCondition = lock.newCondition();
private final Condition normalCondition = lock.newCondition();
public void makeTea(String customer, boolean isVip) {
lock.lock(); // 手动获取锁
try {
// VIP优先处理
if (isVip) {
System.out.println("✨VIP通道:" + customer);
}
while (teaCount >= 5) { // 库存上限
System.out.println(customer + "等待制作台空闲...");
// 进入不同等待区
if (isVip) vipCondition.await();
else normalCondition.await();
}
teaCount++;
System.out.println(customer + "开始制作,库存:" + teaCount);
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(customer + "被中断");
} finally {
lock.unlock(); // 必须手动释放
}
}
public void restock() {
lock.lock();
try {
teaCount = 0; // 清空库存
System.out.println("---原料补充完成---");
// 唤醒所有VIP顾客
vipCondition.signalAll();
// 只唤醒一个普通顾客
normalCondition.signal();
} finally {
lock.unlock();
}
}
}
⚙️ 底层机制(AQS实现)
java
// AbstractQueuedSynchronizer (AQS) 核心结构
public abstract class AQS {
volatile int state; // 锁状态:0=未锁定,>0=锁定
// CLH队列(线程等待队列)
volatile Node head;
volatile Node tail;
// ReentrantLock获取锁伪代码
final void lock() {
if (compareAndSetState(0, 1)) // CAS尝试获取
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 加入等待队列
}
// 公平锁实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 关键区别:检查是否有前辈在等待
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 重入锁实现
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
setState(nextc);
return true;
}
return false;
}
}
✅ ReentrantLock店长的优势
❌ 局限性
- 手动管理:忘记unlock()会导致死锁
- 代码复杂:需要更多模板代码
- 无自动升级:无synchronized的锁升级优化
⚔️ 第三章:终极对决(功能全面对比)
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 锁获取方式 | 自动获取和释放 | 必须手动lock()/unlock() |
| 实现级别 | JVM原生支持(字节码指令) | JDK代码实现(基于AQS) |
| 锁类型 | 非公平锁(可插队) | 可选公平/非公平(构造参数指定) |
| 条件等待 | 单一等待队列(wait/notify) | 多条件队列(newCondition()) |
| 锁中断 | 等待中不可中断 | lockInterruptibly()支持中断等待 |
| 尝试获取锁 | 不支持 | tryLock()支持限时/非阻塞尝试 |
| 锁绑定 | 与对象/方法绑定 | 独立锁对象 |
| 性能 | Java 6后优化良好 | 高竞争下表现更好 |
| 重入性 | 支持 | 支持(重入次数记录在state) |
| 代码复杂度 | 简单(语法糖) | 复杂(需try-finally保证解锁) |
| 适用场景 | 简单同步需求 | 复杂同步策略 |
🧪 第四章:实战代码演示
场景:银行转账(避免死锁)
java
import java.util.concurrent.locks.*;
class BankAccount {
private final ReentrantLock lock = new ReentrantLock();
private int balance;
// 安全转账方法
void transfer(BankAccount to, int amount) {
// 获取两个账户的锁(避免死锁)
while (true) {
if (this.lock.tryLock()) {
try {
if (to.lock.tryLock()) {
try {
this.balance -= amount;
to.balance += amount;
return;
} finally {
to.lock.unlock();
}
}
} finally {
this.lock.unlock();
}
}
// 随机退避避免活锁
try { Thread.sleep((long)(Math.random()*10)); }
catch (InterruptedException e) {}
}
}
}
// 对比synchronized版本(可能死锁)
void unsafeTransfer(BankAccount to, int amount) {
synchronized(this) {
synchronized(to) {
this.balance -= amount;
to.balance += amount;
}
}
}
场景:多条件的生产者-消费者
java
class TeaFactory {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final String[] teas = new String[10];
private int count, putIndex, takeIndex;
public void produce(String tea) throws InterruptedException {
lock.lock();
try {
while (count == teas.length)
notFull.await(); // 仓库满时等待
teas[putIndex] = tea;
if (++putIndex == teas.length) putIndex = 0;
count++;
notEmpty.signal(); // 唤醒消费者
} finally {
lock.unlock();
}
}
public String consume() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 仓库空时等待
String tea = teas[takeIndex];
if (++takeIndex == teas.length) takeIndex = 0;
count--;
notFull.signal(); // 唤醒生产者
return tea;
} finally {
lock.unlock();
}
}
}
🏆 第五章:如何选择?
选择 synchronized 当:
- 简单的同步需求
- 方法级别的同步
- 不想管理锁的获取/释放
- 内存敏感场景(对象头开销小)
选择 ReentrantLock 当:
- 需要公平锁策略
- 需要多条件等待
- 需要可中断的锁获取
- 需要尝试获取锁(避免死锁)
- 需要获取锁状态信息
性能对比结论:
- 低竞争场景:synchronized更优(JVM优化)
- 高竞争场景:ReentrantLock更优(更细粒度控制)
- Java 6+ :两者性能差距已显著缩小
💎 终极总结
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 本质 | JVM关键字 | JDK类实现 |
| 锁获取 | 自动 | 手动lock()/unlock() |
| 灵活性 | 基础功能 | 高级功能(条件、公平、尝试等) |
| 等待条件 | 单一 | 多条件 |
| 公平性 | 非公平 | 可选公平/非公平 |
| 锁中断 | 不支持 | 支持 |
| 代码复杂度 | 低 | 高 |
| 适用场景 | 简单同步 | 复杂同步策略 |
🌟 一句话记忆:
synchronized是 "自动挡汽车" :简单易用,功能有限ReentrantLock是 "手动挡赛车" :控制精准,性能卓越
下次处理线程同步时,根据你的"路况"选择合适的"车辆"吧!🚗💨