🔒 ReentrantLock vs synchronized:奶茶店的双锁之争

126 阅读5分钟

将用一家"线程茶铺"的故事,带你彻底理解Java两大锁机制的区别。这家店有两位店长:Synchronized店长ReentrantLock店长,他们的管理风格截然不同!

🏪 故事背景:线程茶铺的日常

想象一家奶茶店:

  • 顾客 = 线程(Thread)
  • 制作台 = 共享资源(临界区)
  • 排队规则 = 锁机制

两位店长用不同方式管理制作台的使用权:


🧙 第一章:Synchronized店长(synchronized关键字)

📜 管理特点

  1. 自动门禁系统:顾客进入制作区自动上锁,离开自动解锁
  2. 先到先服务:但允许新顾客插队(非公平锁)
  3. 单一等待区:所有等待顾客都在一个休息区

🧾 工作规则(源码原理)

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店长的优势

deepseek_mermaid_20250626_cdccde.png

❌ 局限性

  1. 单一等待条件:所有等待线程只能在一个休息区
  2. 不可中断等待:线程等待时无法响应中断
  3. 非公平锁:新线程可能插队

🧑‍💼 第二章:ReentrantLock店长(ReentrantLock类)

📜 管理特点

  1. 手动门禁卡:顾客需要刷卡进入,刷卡离开
  2. 公平模式可选:可配置严格排队或允许插队
  3. 多等待区:可创建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店长的优势

deepseek_mermaid_20250626_24e614.png

❌ 局限性

  1. 手动管理:忘记unlock()会导致死锁
  2. 代码复杂:需要更多模板代码
  3. 无自动升级:无synchronized的锁升级优化

⚔️ 第三章:终极对决(功能全面对比)

特性synchronizedReentrantLock
锁获取方式自动获取和释放必须手动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 当:

  1. 简单的同步需求
  2. 方法级别的同步
  3. 不想管理锁的获取/释放
  4. 内存敏感场景(对象头开销小)

选择 ReentrantLock 当:

  1. 需要公平锁策略
  2. 需要多条件等待
  3. 需要可中断的锁获取
  4. 需要尝试获取锁(避免死锁)
  5. 需要获取锁状态信息

性能对比结论:

  • 低竞争场景:synchronized更优(JVM优化)
  • 高竞争场景:ReentrantLock更优(更细粒度控制)
  • Java 6+ :两者性能差距已显著缩小

💎 终极总结

维度synchronizedReentrantLock
本质JVM关键字JDK类实现
锁获取自动手动lock()/unlock()
灵活性基础功能高级功能(条件、公平、尝试等)
等待条件单一多条件
公平性非公平可选公平/非公平
锁中断不支持支持
代码复杂度
适用场景简单同步复杂同步策略

🌟 一句话记忆

  • synchronized 是  "自动挡汽车" :简单易用,功能有限
  • ReentrantLock 是  "手动挡赛车" :控制精准,性能卓越

下次处理线程同步时,根据你的"路况"选择合适的"车辆"吧!🚗💨