1.5w字ReentrantLock 深度解析

第一章:ReentrantLock 入门

1. 为什么需要ReentrantLock

在Java早期版本中,synchronized是唯一的同步机制。然而,随着并发编程需求的日益复杂,synchronized的局限性逐渐显现,JDK 1.5引入了java.util.concurrent.locks包,其中ReentrantLock成为了最重要的显式锁实现。

1.1 synchronized的局限性

synchronized是Java内置的同步关键字,使用简单,但存在以下局限:

java 复制代码
/**
 * synchronized的局限性演示
 */
public class SynchronizedLimitations {
    
    private final Object lock = new Object();
    
    /**
     * 局限1:无法中断等待
     * 线程在等待synchronized锁时,无法响应中断
     */
    public void cannotInterrupt() {
        synchronized (lock) {
            // 如果其他线程持有锁很长时间,当前线程只能一直等待
            // 即使调用了 thread.interrupt(),也无法中断等待
        }
    }
    
    /**
     * 局限2:无法设置超时
     * 必须无限期等待,直到获取到锁
     */
    public void cannotTimeout() {
        synchronized (lock) {
            // 无法指定"如果3秒内获取不到锁就放弃"
        }
    }
    
    /**
     * 局限3:无法尝试获取
     * 不能非阻塞地尝试获取锁
     */
    public void cannotTryLock() {
        // 无法实现:如果锁可用就获取,否则立即返回
        synchronized (lock) {
            // ...
        }
    }
    
    /**
     * 局限4:只支持单一条件
     * Object的wait/notify只能有一个等待集合
     */
    public void singleCondition() throws InterruptedException {
        synchronized (lock) {
            // 只能调用 lock.wait(),所有等待线程共用一个队列
            // 无法区分"等待空间"和"等待数据"的线程
            lock.wait();
        }
    }
    
    /**
     * 局限5:无法查询锁状态
     * 不知道锁是否被其他线程持有
     */
    public void cannotQueryState() {
        // 无法判断锁是否被占用
        // 无法知道当前线程的重入次数
    }
}

1.2 ReentrantLock的优势

ReentrantLock完美解决了上述所有问题:

1.3 什么时候选择ReentrantLock

场景 推荐选择 原因
简单同步,性能要求不高 synchronized 语法简单,自动释放
需要可中断等待 ReentrantLock lockInterruptibly()
需要超时获取 ReentrantLock tryLock(timeout)
需要公平锁 ReentrantLock new ReentrantLock(true)
需要多个等待条件 ReentrantLock newCondition()
需要查询锁状态 ReentrantLock isLocked()等方法
锁粒度需要更细 ReentrantLock 更灵活的加锁/解锁控制

2. ReentrantLock核心概念

2.1 什么是ReentrantLock

ReentrantLock是一个可重入的互斥锁(Reentrant Mutual Exclusion Lock)。让我们拆解这个定义:

  • 可重入(Reentrant):同一个线程可以多次获取同一把锁,不会造成死锁
  • 互斥(Mutual Exclusion):同一时刻只有一个线程能持有锁
  • 锁(Lock):用于控制对共享资源的访问
java 复制代码
/**
 * 可重入特性演示
 */
public class ReentrantDemo {
    
    private final ReentrantLock lock = new ReentrantLock();
    
    public void outer() {
        lock.lock();  // 第一次获取锁,holdCount = 1
        try {
            System.out.println("outer方法获取锁");
            inner();  // 调用内部方法,再次获取同一把锁
        } finally {
            lock.unlock();  // 释放锁,holdCount = 0
        }
    }
    
    public void inner() {
        lock.lock();  // 第二次获取锁,holdCount = 2(可重入!)
        try {
            System.out.println("inner方法获取锁");
        } finally {
            lock.unlock();  // 释放锁,holdCount = 1
        }
    }
    
    public static void main(String[] args) {
        ReentrantDemo demo = new ReentrantDemo();
        demo.outer();  // 不会死锁!
    }
}

2.2 公平锁与非公平锁

ReentrantLock支持两种获取锁的策略

非公平锁(默认):

  • 新线程可以"插队",直接尝试获取锁
  • 优点:吞吐量高,减少线程切换开销
  • 缺点:可能导致某些线程"饥饿"(长期等待)

公平锁

  • 严格按照先来后到的顺序获取锁
  • 优点:所有线程都能公平获得执行机会
  • 缺点:吞吐量较低,需要频繁的线程切换
java 复制代码
// 非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock();
ReentrantLock unfairLock2 = new ReentrantLock(false);

// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);

2.3 类结构概览


3. 基础API详解

3.1 API总览

方法 说明 阻塞 可中断 可超时
lock() 获取锁
lockInterruptibly() 可中断地获取锁
tryLock() 尝试获取锁(非阻塞) - -
tryLock(time, unit) 超时获取锁
unlock() 释放锁 - - -
newCondition() 创建条件变量 - - -

3.2 lock() - 获取锁

lock()是最基本的获取锁方法。如果锁被其他线程持有,当前线程会阻塞等待,直到获取到锁。

java 复制代码
/**
 * lock()方法使用示例
 */
public class LockExample {
    
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;
    
    /**
     * 标准使用模式:lock-try-finally-unlock
     */
    public void increment() {
        lock.lock();  // 获取锁
        try {
            count++;  // 临界区代码
        } finally {
            lock.unlock();  // 必须在finally中释放锁!
        }
    }
    
    /**
     * 错误示例:没有在finally中释放锁
     */
    public void wrongUsage() {
        lock.lock();
        count++;
        lock.unlock();  // 如果count++抛异常,锁永远不会释放!
    }
}

重要警告: lock()unlock()必须配对使用,且unlock()必须放在finally块中,确保无论是否发生异常都能释放锁。

3.3 lockInterruptibly() - 可中断获取锁

当线程在等待锁时,可以响应中断。这在需要取消长时间等待的场景非常有用。

java 复制代码
/**
 * lockInterruptibly()使用示例
 */
public class InterruptibleLockExample {
    
    private final ReentrantLock lock = new ReentrantLock();
    
    /**
     * 可中断的获取锁
     */
    public void interruptibleMethod() throws InterruptedException {
        // 如果线程被中断,会抛出InterruptedException
        lock.lockInterruptibly();
        try {
            // 执行业务逻辑
            doSomething();
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 实际应用:可取消的任务
     */
    public void cancellableTask() {
        Thread worker = new Thread(() -> {
            try {
                lock.lockInterruptibly();
                try {
                    // 长时间运行的任务
                    while (!Thread.currentThread().isInterrupted()) {
                        processData();
                    }
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                System.out.println("任务被取消");
                Thread.currentThread().interrupt();  // 恢复中断状态
            }
        });
        
        worker.start();
        
        // 需要取消时
        // worker.interrupt();  // 会使lockInterruptibly()抛出异常
    }
    
    private void doSomething() { /* ... */ }
    private void processData() { /* ... */ }
}

3.4 tryLock() - 尝试获取锁

tryLock()是非阻塞的获取锁方法。如果锁可用,立即获取并返回true;否则立即返回false

java 复制代码
/**
 * tryLock()使用示例
 */
public class TryLockExample {
    
    private final ReentrantLock lock = new ReentrantLock();
    
    /**
     * 非阻塞获取锁
     */
    public boolean tryProcess() {
        if (lock.tryLock()) {  // 尝试获取锁
            try {
                // 获取成功,执行业务逻辑
                doProcess();
                return true;
            } finally {
                lock.unlock();
            }
        } else {
            // 获取失败,执行降级逻辑
            System.out.println("锁被占用,执行降级操作");
            return false;
        }
    }
    
    /**
     * 实际应用:避免死锁的转账操作
     */
    public boolean transfer(Account from, Account to, int amount) {
        // 尝试同时获取两把锁,避免死锁
        while (true) {
            if (from.lock.tryLock()) {
                try {
                    if (to.lock.tryLock()) {
                        try {
                            // 两把锁都获取成功,执行转账
                            from.debit(amount);
                            to.credit(amount);
                            return true;
                        } finally {
                            to.lock.unlock();
                        }
                    }
                } finally {
                    from.lock.unlock();
                }
            }
            // 获取失败,短暂休眠后重试
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
    }
    
    private void doProcess() { /* ... */ }
}

class Account {
    final ReentrantLock lock = new ReentrantLock();
    private int balance;
    
    void debit(int amount) { balance -= amount; }
    void credit(int amount) { balance += amount; }
}

3.5 tryLock(long time, TimeUnit unit) - 超时获取锁

在指定时间内尝试获取锁。如果在超时时间内获取到锁,返回true;否则返回false

java 复制代码
/**
 * tryLock(timeout)使用示例
 */
public class TimeoutLockExample {
    
    private final ReentrantLock lock = new ReentrantLock();
    
    /**
     * 超时获取锁
     */
    public boolean processWithTimeout() {
        try {
            // 尝试在3秒内获取锁
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                try {
                    doProcess();
                    return true;
                } finally {
                    lock.unlock();
                }
            } else {
                // 超时未获取到锁
                System.out.println("获取锁超时");
                return false;
            }
        } catch (InterruptedException e) {
            // 等待过程中被中断
            System.out.println("等待锁时被中断");
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    /**
     * 实际应用:带超时的分布式锁模拟
     */
    public boolean executeWithLock(Runnable task, long timeout, TimeUnit unit) {
        try {
            if (lock.tryLock(timeout, unit)) {
                try {
                    task.run();
                    return true;
                } finally {
                    lock.unlock();
                }
            }
            return false;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    private void doProcess() { /* ... */ }
}

3.6 unlock() - 释放锁

释放锁,将锁的持有计数减1。当计数变为0时,锁完全释放,其他等待线程可以竞争获取。

java 复制代码
/**
 * unlock()使用示例
 */
public class UnlockExample {
    
    private final ReentrantLock lock = new ReentrantLock();
    
    /**
     * 可重入场景下的unlock
     */
    public void reentrantExample() {
        lock.lock();  // holdCount = 1
        try {
            System.out.println("外层锁定,holdCount = " + lock.getHoldCount());
            
            lock.lock();  // holdCount = 2
            try {
                System.out.println("内层锁定,holdCount = " + lock.getHoldCount());
            } finally {
                lock.unlock();  // holdCount = 1
                System.out.println("内层释放,holdCount = " + lock.getHoldCount());
            }
            
        } finally {
            lock.unlock();  // holdCount = 0,锁完全释放
            System.out.println("外层释放,holdCount = " + lock.getHoldCount());
        }
    }
    
    /**
     *  错误:非锁持有者调用unlock
     */
    public void wrongUnlock() {
        // 没有获取锁就调用unlock,会抛出IllegalMonitorStateException
        try {
            lock.unlock();  //  抛出异常!
        } catch (IllegalMonitorStateException e) {
            System.out.println("非锁持有者不能释放锁: " + e.getMessage());
        }
    }
}

3.7 newCondition() - 创建条件变量

创建与锁关联的Condition对象,用于实现等待/通知机制。一把锁可以创建多个Condition,这是相比synchronized的重要优势。

java 复制代码
/**
 * newCondition()使用示例
 */
public class ConditionExample {
    
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();  // 非空条件
    private final Condition notFull = lock.newCondition();   // 非满条件
    
    private final Object[] items = new Object[10];
    private int putIndex, takeIndex, count;
    
    /**
     * 生产者:添加元素
     */
    public void put(Object item) throws InterruptedException {
        lock.lock();
        try {
            // 队列满时,等待notFull条件
            while (count == items.length) {
                notFull.await();
            }
            items[putIndex] = item;
            if (++putIndex == items.length) putIndex = 0;
            count++;
            // 通知消费者:队列非空了
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 消费者:取出元素
     */
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            // 队列空时,等待notEmpty条件
            while (count == 0) {
                notEmpty.await();
            }
            Object item = items[takeIndex];
            items[takeIndex] = null;
            if (++takeIndex == items.length) takeIndex = 0;
            count--;
            // 通知生产者:队列非满了
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

3.8 其他辅助方法

java 复制代码
/**
 * ReentrantLock的辅助方法
 */
public class AuxiliaryMethodsExample {
    
    private final ReentrantLock lock = new ReentrantLock(true);  // 公平锁
    
    public void demonstrateAuxiliaryMethods() {
        lock.lock();
        try {
            // 查询锁是否被任何线程持有
            boolean locked = lock.isLocked();
            System.out.println("锁是否被持有: " + locked);  // true
            
            // 查询当前线程的持有计数
            int holdCount = lock.getHoldCount();
            System.out.println("当前线程持有次数: " + holdCount);  // 1
            
            // 查询当前线程是否持有锁
            boolean heldByMe = lock.isHeldByCurrentThread();
            System.out.println("当前线程是否持有锁: " + heldByMe);  // true
            
            // 查询是否为公平锁
            boolean fair = lock.isFair();
            System.out.println("是否为公平锁: " + fair);  // true
            
            // 查询是否有线程在等待获取锁
            boolean hasWaiters = lock.hasQueuedThreads();
            System.out.println("是否有等待线程: " + hasWaiters);
            
            // 查询等待队列长度
            int queueLength = lock.getQueueLength();
            System.out.println("等待队列长度: " + queueLength);
            
        } finally {
            lock.unlock();
        }
    }
}

4. 典型使用场景

4.1 场景一:线程安全的计数器

这是最基础的使用场景,确保多线程环境下计数操作的原子性。

java 复制代码
/**
 * 线程安全的计数器
 */
public class ThreadSafeCounter {
    
    private final ReentrantLock lock = new ReentrantLock();
    private long count = 0;
    
    /**
     * 原子递增
     */
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 原子递减
     */
    public void decrement() {
        lock.lock();
        try {
            count--;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 获取当前值(需要加锁保证可见性)
     */
    public long getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 如果当前值等于预期值,则更新为新值
     */
    public boolean compareAndSet(long expected, long newValue) {
        lock.lock();
        try {
            if (count == expected) {
                count = newValue;
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }
}

4.2 场景二:可中断的资源获取

在需要能够取消长时间等待的场景中使用lockInterruptibly()

java 复制代码
/**
 * 可中断的资源池
 */
public class InterruptibleResourcePool {
    
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition available = lock.newCondition();
    private final List<Resource> resources = new ArrayList<>();
    private final int maxSize;
    
    public InterruptibleResourcePool(int maxSize) {
        this.maxSize = maxSize;
        for (int i = 0; i < maxSize; i++) {
            resources.add(new Resource(i));
        }
    }
    
    /**
     * 可中断地获取资源
     * 支持在等待过程中取消
     */
    public Resource acquire() throws InterruptedException {
        lock.lockInterruptibly();  // 可中断地获取锁
        try {
            // 等待可用资源
            while (resources.isEmpty()) {
                available.await();  // 可中断地等待
            }
            return resources.remove(0);
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 归还资源
     */
    public void release(Resource resource) {
        lock.lock();
        try {
            resources.add(resource);
            available.signal();  // 通知等待线程
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 使用示例
     */
    public static void main(String[] args) {
        InterruptibleResourcePool pool = new InterruptibleResourcePool(2);
        
        Thread worker = new Thread(() -> {
            try {
                Resource resource = pool.acquire();
                try {
                    // 使用资源
                    resource.use();
                } finally {
                    pool.release(resource);
                }
            } catch (InterruptedException e) {
                System.out.println("资源获取被取消");
            }
        });
        
        worker.start();
        
        // 可以在需要时取消等待
        // worker.interrupt();
    }
}

class Resource {
    private final int id;
    
    Resource(int id) { this.id = id; }
    
    void use() {
        System.out.println("使用资源: " + id);
    }
}

4.3 场景三:超时获取锁避免死锁

在可能发生死锁的场景中,使用tryLock(timeout)来避免永久阻塞。

java 复制代码
/**
 * 安全的银行转账(避免死锁)
 */
public class SafeBankTransfer {
    
    /**
     * 带超时的转账操作
     * 通过超时机制避免死锁
     */
    public boolean transfer(BankAccount from, BankAccount to, 
                           double amount, long timeout, TimeUnit unit) {
        long deadline = System.nanoTime() + unit.toNanos(timeout);
        
        while (true) {
            if (from.lock.tryLock()) {
                try {
                    // 计算剩余超时时间
                    long remaining = deadline - System.nanoTime();
                    if (remaining <= 0) {
                        return false;  // 超时
                    }
                    
                    // 尝试获取第二把锁
                    if (to.lock.tryLock(remaining, TimeUnit.NANOSECONDS)) {
                        try {
                            // 检查余额
                            if (from.getBalance() < amount) {
                                throw new InsufficientFundsException();
                            }
                            // 执行转账
                            from.debit(amount);
                            to.credit(amount);
                            return true;
                        } finally {
                            to.lock.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return false;
                } finally {
                    from.lock.unlock();
                }
            }
            
            // 获取失败,短暂休眠后重试
            if (System.nanoTime() >= deadline) {
                return false;  // 超时
            }
            
            try {
                Thread.sleep(10);  // 避免忙等待
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
    }
}

class BankAccount {
    final ReentrantLock lock = new ReentrantLock();
    private double balance;
    
    public BankAccount(double balance) {
        this.balance = balance;
    }
    
    public double getBalance() { return balance; }
    public void debit(double amount) { balance -= amount; }
    public void credit(double amount) { balance += amount; }
}

class InsufficientFundsException extends RuntimeException {
    public InsufficientFundsException() {
        super("余额不足");
    }
}

4.4 场景四:公平锁保证顺序

在需要严格按照请求顺序处理的场景中使用公平锁。

java 复制代码
/**
 * 公平的打印队列
 * 确保打印任务按提交顺序执行
 */
public class FairPrintQueue {
    
    // 使用公平锁,保证FIFO顺序
    private final ReentrantLock lock = new ReentrantLock(true);
    
    /**
     * 打印文档
     */
    public void printJob(String document) {
        lock.lock();
        try {
            System.out.printf("[%s] 开始打印: %s%n", 
                Thread.currentThread().getName(), document);
            
            // 模拟打印耗时
            simulatePrinting();
            
            System.out.printf("[%s] 完成打印: %s%n", 
                Thread.currentThread().getName(), document);
        } finally {
            lock.unlock();
        }
    }
    
    private void simulatePrinting() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    public static void main(String[] args) {
        FairPrintQueue queue = new FairPrintQueue();
        
        // 创建10个打印任务
        for (int i = 0; i < 10; i++) {
            final int docNum = i;
            new Thread(() -> {
                queue.printJob("文档-" + docNum);
            }, "Thread-" + i).start();
        }
    }
}

5. ReentrantLock vs synchronized

5.1 功能对比

特性 ReentrantLock synchronized
实现方式 JDK实现(纯Java) JVM内置(native)
锁获取方式 显式lock()/unlock() 隐式,进入/退出代码块
可中断等待 ✅ lockInterruptibly()
超时获取 ✅ tryLock(timeout)
非阻塞获取 ✅ tryLock()
公平锁 ✅ 构造参数可选 ❌ 非公平
条件变量 ✅ 多个Condition ❌ 单一wait/notify
锁状态查询 ✅ isLocked()等
自动释放 ❌ 必须手动unlock ✅ 自动释放
锁绑定对象 ReentrantLock实例 任意对象

5.2 性能对比

  • JDK 1.5: ReentrantLock性能远优于synchronized
  • JDK 1.6+: synchronized经过优化(偏向锁、轻量级锁),性能差距缩小
  • 低竞争场景: synchronized略优(少了lock对象的开销)
  • 高竞争场景: ReentrantLock更可控(可选公平锁、可中断)

5.3 代码对比

java 复制代码
/**
 * synchronized vs ReentrantLock 代码对比
 */
public class LockComparison {
    
    private final Object syncLock = new Object();
    private final ReentrantLock reentrantLock = new ReentrantLock();
    
    // ========== 基本用法对比 ==========
    
    public void synchronizedMethod() {
        synchronized (syncLock) {
            // 临界区
        }
    }
    
    public void reentrantLockMethod() {
        reentrantLock.lock();
        try {
            // 临界区
        } finally {
            reentrantLock.unlock();
        }
    }
    
    // ========== 等待/通知对比 ==========
    
    public void synchronizedWait() throws InterruptedException {
        synchronized (syncLock) {
            syncLock.wait();     // 等待
            syncLock.notify();   // 通知单个
            syncLock.notifyAll();// 通知所有
        }
    }
    
    private final Condition condition = reentrantLock.newCondition();
    
    public void reentrantLockWait() throws InterruptedException {
        reentrantLock.lock();
        try {
            condition.await();     // 等待
            condition.signal();    // 通知单个
            condition.signalAll(); // 通知所有
        } finally {
            reentrantLock.unlock();
        }
    }
}

5.4 选择建议

选择synchronized的情况

  • 简单同步需求
  • 不需要高级特性
  • 追求代码简洁
  • 团队对ReentrantLock不熟悉

选择ReentrantLock的情况

  • 需要可中断等待
  • 需要超时获取
  • 需要公平锁
  • 需要多个条件变量
  • 需要查询锁状态
  • 需要更细粒度的锁控制

5.5 异常处理的关键区别

面试中常被忽略但非常重要的区别:

java 复制代码
/**
 * 异常处理对比 - 这是synchronized的重要优势
 */
public class ExceptionHandlingComparison {
    
    private final Object syncLock = new Object();
    private final ReentrantLock reentrantLock = new ReentrantLock();
    
    /**
     * synchronized:异常时自动释放锁
     * 无论是正常退出还是异常退出,锁都会被自动释放
     */
    public void synchronizedWithException() {
        synchronized (syncLock) {
            // 即使这里抛出异常,锁也会自动释放
            throw new RuntimeException("出错了");
        }
        // 锁已自动释放,其他线程可以获取
    }
    
    /**
     * ReentrantLock:必须在finally中释放锁
     * 如果忘记finally或unlock,锁将永远不会释放!
     */
    public void reentrantLockWithException() {
        reentrantLock.lock();
        try {
            // 如果这里抛出异常...
            throw new RuntimeException("出错了");
        } finally {
            // 必须在finally中释放!
            reentrantLock.unlock();
        }
    }
    
    /**
     * 【危险】错误示范:忘记finally
     */
    public void dangerousCode() {
        reentrantLock.lock();
        // 如果下面代码抛出异常,锁将永远不会释放!
        doSomethingDangerous();
        reentrantLock.unlock(); // 这行可能永远不会执行
    }
    
    private void doSomethingDangerous() {
        throw new RuntimeException();
    }
}

核心区别

  • synchronized:JVM保证锁的释放,开发者无需关心
  • ReentrantLock:开发者全权负责,必须配合try-finally使用

5.6 为什么有时synchronized更好

虽然ReentrantLock功能更强大,但以下场景synchronized是更好的选择:

java 复制代码
/**
 * synchronized更适合的场景
 */
public class WhenToUseSynchronized {
    
    /**
     * 场景1:简单的同步方法
     * synchronized更简洁,不需要try-finally
     */
    public synchronized void simpleMethod() {
        // 一行搞定,简洁明了
    }
    
    /**
     * 场景2:同步代码块很短
     * JVM对synchronized有偏向锁、轻量级锁优化
     */
    public void shortCriticalSection() {
        synchronized (this) {
            counter++;  // 就这一行,用synchronized足够
        }
    }
    
    private int counter;
    
    /**
     * 场景3:不需要ReentrantLock的高级特性
     * 如果不需要中断、超时、公平锁、多条件变量
     * 用synchronized更安全(不会忘记释放锁)
     */
    public synchronized void noAdvancedFeatures() {
        // 普通业务逻辑
    }
    
    /**
     * 场景4:锁的粒度就是整个方法
     * synchronized方法天然适合
     */
    public synchronized void entireMethodNeedLock() {
        step1();
        step2();
        step3();
    }
    
    private void step1() {}
    private void step2() {}
    private void step3() {}
}

总结

场景 推荐 原因
简单同步 synchronized 语法简洁,不易出错
需要中断/超时 ReentrantLock synchronized不支持
需要公平锁 ReentrantLock synchronized只有非公平
多个等待条件 ReentrantLock synchronized只有一个wait set
性能敏感+低竞争 synchronized JVM优化更好
遗留代码维护 synchronized 保持一致性

5.7 锁的优缺点总结

在选择同步机制之前,先理解锁本身的优缺点:

锁的优点

  1. 简单直观:比无锁编程容易理解和实现
  2. 功能完整:支持复杂的同步逻辑
  3. 可组合性:多个操作可以原子化执行
java 复制代码
// 锁可以保护复杂的复合操作
lock.lock();
try {
    if (map.containsKey(key)) {
        map.put(key, map.get(key) + 1);
    } else {
        map.put(key, 1);
    }
} finally {
    lock.unlock();
}

锁的缺点

  1. 性能开销:线程阻塞/唤醒涉及系统调用和上下文切换
  2. 死锁风险:多把锁可能造成死锁
  3. 优先级反转:低优先级线程持有锁时,高优先级线程被迫等待
  4. 不可组合:两个线程安全的操作组合后可能不安全
java 复制代码
// 不可组合的例子:两个线程安全操作组合后不安全
// 假设list是CopyOnWriteArrayList(线程安全)
if (!list.contains(item)) {  // 操作1:线程安全
    list.add(item);           // 操作2:线程安全
}
// 但整个if-then-add不是原子的!需要额外的锁

何时考虑其他方案

场景 推荐方案 原因
简单计数 AtomicInteger 无锁,性能更好
简单标志位 volatile 足够保证可见性
读多写少 ReadWriteLock/StampedLock 读操作并发
累加统计 LongAdder 高竞争下性能更好
单次初始化 DCL或Holder模式 避免每次加锁

第二章:AQS 核心原理剖析

1. AQS设计思想

1.1 什么是AQS

AQS(AbstractQueuedSynchronizer)是Doug Lea设计的一个用于构建锁和同步器的框架。JUC(java.util.concurrent)包中的大部分同步器都是基于AQS实现的:

AQS的核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效工作线程,并将共享资源设置为锁定状态;如果被请求的共享资源被占用,就需要一套线程阻塞等待以及被唤醒时锁分配的机制,AQS使用CLH队列锁的变体来实现这一机制。

1.2 模板方法模式

AQS采用模板方法设计模式:将通用的流程封装在父类中,将可变的部分留给子类实现。

java 复制代码
// AQS定义的模板方法(final,不可重写)
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&           // ① 子类实现:尝试获取锁
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  // ② AQS实现:入队等待
        selfInterrupt();
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {            // ① 子类实现:尝试释放锁
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);       // ② AQS实现:唤醒后继
        return true;
    }
    return false;
}

子类需要实现的方法

方法 说明 使用场景
tryAcquire(int) 尝试独占获取 ReentrantLock
tryRelease(int) 尝试独占释放 ReentrantLock
tryAcquireShared(int) 尝试共享获取 Semaphore、CountDownLatch
tryReleaseShared(int) 尝试共享释放 Semaphore、CountDownLatch
isHeldExclusively() 是否独占持有 Condition使用

1.3 两种资源共享模式

AQS支持两种资源共享模式:

独占模式(Exclusive)

  • 同一时刻只有一个线程能获取资源
  • 例如:ReentrantLock

共享模式(Shared)

  • 多个线程可以同时获取资源
  • 例如:Semaphore(信号量)、CountDownLatch、ReadWriteLock的读锁

2. 核心字段解析

2.1 state字段

state是AQS中最核心的字段,表示同步状态。它的含义由具体的同步器定义:

java 复制代码
/**
 * The synchronization state.
 */
private volatile int state;

// 获取状态
protected final int getState() {
    return state;
}

// 设置状态
protected final void setState(int newState) {
    state = newState;
}

// CAS设置状态
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

不同同步器中state的含义

同步器 state含义
ReentrantLock 0=未锁定,>0=锁定(值表示重入次数)
ReentrantReadWriteLock 高16位=读锁数量,低16位=写锁重入次数
Semaphore 可用许可数量
CountDownLatch 剩余计数
java 复制代码
/**
 * ReentrantLock中state的使用
 */
// 获取锁时
if (compareAndSetState(0, 1)) {  // 0 -> 1,获取成功
    setExclusiveOwnerThread(current);
}

// 重入时
int nextc = c + acquires;  // state递增
setState(nextc);

// 释放锁时
int c = getState() - releases;  // state递减
if (c == 0) {
    setExclusiveOwnerThread(null);  // 完全释放
}
setState(c);

2.2 head和tail字段

AQS使用一个FIFO双向队列来管理等待线程。headtail分别指向队列的头部和尾部:

java 复制代码
/**
 * Head of the wait queue, lazily initialized.
 */
private transient volatile Node head;

/**
 * Tail of the wait queue, lazily initialized.
 */
private transient volatile Node tail;

关键点

  • 懒初始化:只有在发生竞争时才会初始化队列
  • head是哨兵节点:不存储实际的等待线程,简化边界处理
  • 双向链表:便于节点的取消和超时处理

2.3 exclusiveOwnerThread字段

该字段继承自AbstractOwnableSynchronizer,记录当前持有独占锁的线程:

java 复制代码
// AbstractOwnableSynchronizer.java
public abstract class AbstractOwnableSynchronizer {
    
    private transient Thread exclusiveOwnerThread;
    
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

这个字段用于:

  1. 可重入判断:检查当前线程是否已经持有锁
  2. 锁状态查询:获取锁的持有者

3. CLH队列变体

3.1 经典CLH队列

CLH(Craig, Landin, and Hagersten)队列是一种基于链表的自旋锁,每个线程在前驱节点上自旋等待:

经典CLH特点

  • 每个节点只有一个locked标志
  • 后继节点在前驱的locked上自旋
  • 释放锁时,只需设置自己的locked=false

3.2 AQS的CLH变体

AQS对CLH队列进行了改造,主要区别:

主要改进

特性 经典CLH AQS CLH变体
链表方向 单向(隐式) 双向(显式prev/next)
等待方式 自旋 park/unpark阻塞
节点状态 简单locked 多种waitStatus
取消支持 不支持 支持(CANCELLED状态)
超时支持 不支持 支持

3.3 为什么使用双向链表

单向链表的问题

  1. 取消节点时,无法快速找到前驱来修改next指针
  2. 超时/中断时需要从头遍历

双向链表的优势

  1. O(1)时间找到前驱节点
  2. 便于实现取消和超时逻辑
  3. 唤醒后继时更高效
java 复制代码
// 取消节点时需要找到有效前驱
private void cancelAcquire(Node node) {
    // ...
    Node pred = node.prev;
    while (pred.waitStatus > 0)  // 跳过已取消的前驱
        node.prev = pred = pred.prev;
    // ...
}

3.4 自旋锁与AQS的自旋机制

什么是自旋锁?

自旋锁是一种忙等待锁,线程在获取锁失败时不会立即阻塞,而是在循环中不断尝试获取锁:

java 复制代码
/**
 * 简单自旋锁示例
 */
public class SimpleSpinLock {
    private AtomicBoolean locked = new AtomicBoolean(false);
    
    public void lock() {
        // 自旋:不断尝试,直到成功
        while (!locked.compareAndSet(false, true)) {
            // 自旋等待,消耗CPU
        }
    }
    
    public void unlock() {
        locked.set(false);
    }
}

自旋的优缺点

方面 优点 缺点
CPU使用 无上下文切换 持续消耗CPU
延迟 锁释放后立即获取 长时间等待浪费CPU
适用场景 锁持有时间短 锁持有时间长时效率低

AQS中的自旋机制

AQS并非纯自旋锁 ,而是采用自旋+阻塞的混合策略:

java 复制代码
/**
 * acquireQueued中的自旋逻辑
 * 关键:不是一直自旋,而是有条件地自旋
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {  // 这是一个"自旋"循环
            final Node p = node.predecessor();
            
            // 【自旋尝试1】只有前驱是head时才尝试获取锁
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null;
                failed = false;
                return interrupted;
            }
            
            // 【关键】不满足条件时,不是继续自旋,而是park阻塞!
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

AQS自旋策略的精妙之处

  1. 有限自旋:只有前驱是head(即马上轮到自己)时才自旋尝试
  2. 快速阻塞:其他情况直接park阻塞,避免CPU空转
  3. 唤醒后自旋:被unpark唤醒后,再次进入循环尝试
java 复制代码
// 为什么要在park前再自旋一次?
// 因为在设置SIGNAL和park之间,锁可能已经释放了
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;  // 可以安全阻塞
    // ... 设置SIGNAL
    return false;  // 返回false,外层循环会再尝试一次acquire!
}

3.5 锁的内存语义

从 JMM 的角度看,锁既提供互斥,也提供内存可见性保障。具体来说:对同一把锁的释放与获取之间存在"happens-before" 关系。一个线程在释放锁时,会把临界区中的所有写操作刷回主内存(类似于对 volatile 变量的写);另一个线程之后获取同一把锁时,必须从主内存中重新读取这些变量,从而看到之前线程的修改,这类似于对 volatile 变量的读。

在 ReentrantLock 内部,这种语义是通过 AQS 的 state 字段(volatile)和 CAS 操作实现的:获取锁使用 CAS 修改 state,相当于一次"volatile 读 + volatile 写";释放锁使用对 state 的普通写,但 state 本身是 volatile 变量,这会把之前的修改刷新到主内存。这样,临界区内的所有普通写在释放锁后,对后续获取同一把锁的线程都是可见的。

为什么理解内存语义很重要?

锁不仅提供互斥,还提供内存可见性保证。这是很多开发者忽略的关键点。

锁的内存语义

  1. 获取锁:相当于读取volatile变量,会清空本地缓存,从主内存重新读取
  2. 释放锁:相当于写入volatile变量,会将本地缓存刷新到主内存
java 复制代码
/**
 * 锁的内存语义示例
 */
public class LockMemorySemantics {
    
    private int x = 0;
    private int y = 0;
    private final ReentrantLock lock = new ReentrantLock();
    
    public void writer() {
        lock.lock();  // 获取锁:后续读操作不会重排序到这之前
        try {
            x = 1;  // 这些写入在释放锁时会刷新到主内存
            y = 2;
        } finally {
            lock.unlock();  // 释放锁:之前的写操作对其他线程可见
        }
    }
    
    public void reader() {
        lock.lock();  // 获取锁:会看到释放锁之前的所有写入
        try {
            // 这里一定能看到 x=1, y=2
            System.out.println(x + ", " + y);
        } finally {
            lock.unlock();
        }
    }
}

AQS如何实现内存语义?

通过volatile变量state和CAS操作:

java 复制代码
// 获取锁时的内存语义
protected final boolean compareAndSetState(int expect, int update) {
    // CAS操作具有volatile读+写的内存语义
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

// 释放锁时的内存语义
protected final void setState(int newState) {
    state = newState;  // volatile写,刷新缓存到主内存
}

volatile vs 锁

特性 volatile
可见性 ✅ 保证 ✅ 保证
原子性 ❌ 只保证单个读/写 ✅ 保证临界区原子性
有序性 ✅ 禁止重排序 ✅ 禁止重排序
复合操作 ❌ 不支持 ✅ 支持
性能 更好(无阻塞) 有阻塞开销
java 复制代码
// volatile适合的场景:状态标志
private volatile boolean shutdown = false;

public void shutdown() {
    shutdown = true;  // 单个写操作,volatile足够
}

public void doWork() {
    while (!shutdown) {  // 单个读操作
        // ...
    }
}

// volatile不适合的场景:复合操作
private volatile int count = 0;

public void increment() {
    count++;  // 【错误】这是读-改-写,不是原子操作!
}

// 正确做法:使用锁或AtomicInteger
private final ReentrantLock lock = new ReentrantLock();

public void safeIncrement() {
    lock.lock();
    try {
        count++;  // 现在是线程安全的
    } finally {
        lock.unlock();
    }
}

4. Node节点详解

4.1 Node结构

java 复制代码
static final class Node {
    /** 共享模式标记 */
    static final Node SHARED = new Node();
    /** 独占模式标记 */
    static final Node EXCLUSIVE = null;
    
    /** waitStatus值:表示线程已取消 */
    static final int CANCELLED =  1;
    /** waitStatus值:表示后继线程需要唤醒 */
    static final int SIGNAL    = -1;
    /** waitStatus值:表示线程在条件队列中等待 */
    static final int CONDITION = -2;
    /** waitStatus值:表示共享模式下无条件传播 */
    static final int PROPAGATE = -3;
    
    /** 等待状态 */
    volatile int waitStatus;
    
    /** 前驱节点 */
    volatile Node prev;
    
    /** 后继节点 */
    volatile Node next;
    
    /** 节点中的线程 */
    volatile Thread thread;
    
    /** 链接下一个等待条件的节点,或特殊值SHARED */
    Node nextWaiter;
}

4.2 waitStatus状态详解

waitStatus是Node中最复杂的字段,用于表示节点的等待状态:

状态值 名称 含义
0 初始状态 新建节点的默认状态
-1 SIGNAL 当前节点释放锁后需要唤醒后继节点
1 CANCELLED 线程已取消(超时或中断)
-2 CONDITION 节点在条件队列中等待
-3 PROPAGATE 共享模式下,释放操作需要传播

SIGNAL状态的重要性

java 复制代码
// 节点入队后,需要将前驱的waitStatus设为SIGNAL
// 这样前驱释放锁时才会唤醒当前节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        // 前驱已经是SIGNAL,可以安全阻塞
        return true;
    if (ws > 0) {
        // 前驱已取消,跳过
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 将前驱状态设为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

4.3 节点模式

java 复制代码
// 判断是否为共享模式
final boolean isShared() {
    return nextWaiter == SHARED;
}

// 创建节点
Node(Thread thread, Node mode) {     // Used by addWaiter
    this.nextWaiter = mode;
    this.thread = thread;
}

Node(Thread thread, int waitStatus) { // Used by Condition
    this.waitStatus = waitStatus;
    this.thread = thread;
}

5. 独占模式获取锁

5.1 acquire流程概览

java 复制代码
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

整体流程如下:

5.2 tryAcquire - 子类实现

以ReentrantLock的非公平锁为例:

java 复制代码
// NonfairSync.tryAcquire
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

// Sync.nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    
    if (c == 0) {
        // 锁空闲,CAS尝试获取
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        // 当前线程已持有锁,重入
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    
    return false;
}

5.3 addWaiter - 创建节点入队

java 复制代码
private Node addWaiter(Node mode) {
    // 创建新节点
    Node node = new Node(Thread.currentThread(), mode);
    
    // 快速路径:尝试直接挂到队尾
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    
    // 快速路径失败,进入完整入队流程
    enq(node);
    return node;
}

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) {
            // 队列为空,初始化(创建哨兵节点)
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 将节点挂到队尾
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

入队过程图解

sequenceDiagram participant T as 当前线程 participant AQS as AQS队列 T->>AQS: addWaiter(EXCLUSIVE) alt 队列不为空 T->>AQS: node.prev = tail T->>AQS: CAS设置tail T->>AQS: oldTail.next = node else 队列为空 T->>AQS: CAS设置head(哨兵) T->>AQS: tail = head T->>AQS: 再次循环入队 end AQS-->>T: 返回node

5.4 acquireQueued - 排队获取锁

这是最核心的方法,线程在队列中自旋或阻塞,直到获取到锁:

java 复制代码
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 获取前驱节点
            final Node p = node.predecessor();
            
            // 如果前驱是head,说明当前节点是第一个等待者,尝试获取锁
            if (p == head && tryAcquire(arg)) {
                // 获取成功,将当前节点设为head
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            
            // 判断是否需要阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

acquireQueued流程图

5.5 shouldParkAfterFailedAcquire - 是否阻塞

java 复制代码
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    
    if (ws == Node.SIGNAL)
        // 前驱状态正常,当前线程可以安全阻塞
        return true;
        
    if (ws > 0) {
        // 前驱已取消,跳过所有已取消的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 将前驱状态设为SIGNAL
        // 下次循环会返回true
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    
    return false;
}

为什么不直接阻塞,而是先设置前驱为SIGNAL?

这是为了确保当前节点阻塞后能被正确唤醒。如果直接阻塞,而前驱的waitStatus不是SIGNAL,那么前驱释放锁时不会唤醒当前节点,导致永久阻塞。

5.6 parkAndCheckInterrupt - 阻塞线程

java 复制代码
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);  // 阻塞当前线程
    return Thread.interrupted();  // 返回并清除中断状态
}

LockSupport.park()会阻塞当前线程,直到以下情况之一发生:

  1. 其他线程调用unpark()唤醒
  2. 线程被中断
  3. 虚假唤醒(spurious wakeup)

6. 独占模式释放锁

6.1 release流程

java 复制代码
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

6.2 tryRelease - 子类实现

java 复制代码
// ReentrantLock.Sync.tryRelease
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    
    // 只有锁持有者才能释放
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    
    boolean free = false;
    if (c == 0) {
        // state减到0,完全释放
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

6.3 unparkSuccessor - 唤醒后继

java 复制代码
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    
    // 找到需要唤醒的后继节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        // next为空或已取消,从tail向前找
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    
    if (s != null)
        LockSupport.unpark(s.thread);  // 唤醒线程
}

为什么要从tail向前找?

因为在addWaiter中,设置prev指针和next指针不是原子操作:

java 复制代码
// addWaiter中的代码
node.prev = pred;               // ① 先设置prev
if (compareAndSetTail(pred, node)) {
    pred.next = node;           // ② 后设置next
    return node;
}

在①和②之间,如果发生并发操作,从head向后遍历可能找不到新入队的节点,但从tail向前遍历一定能找到。

6.4 完整的加锁-释放锁流程

sequenceDiagram participant T1 as 线程1 participant T2 as 线程2 participant AQS as AQS Note over AQS: state=0, head=tail=null T1->>AQS: lock() -> acquire(1) AQS->>AQS: tryAcquire(1): state 0->1 Note over AQS: state=1, owner=T1 AQS-->>T1: 获取成功 T2->>AQS: lock() -> acquire(1) AQS->>AQS: tryAcquire(1): 失败 AQS->>AQS: addWaiter(EXCLUSIVE) Note over AQS: 创建head哨兵和T2节点 AQS->>AQS: acquireQueued() AQS->>AQS: park(T2) Note over T2: 阻塞等待 T1->>AQS: unlock() -> release(1) AQS->>AQS: tryRelease(1): state 1->0 Note over AQS: state=0, owner=null AQS->>AQS: unparkSuccessor(head) AQS->>T2: unpark(T2) Note over T2: 被唤醒 T2->>AQS: 继续acquireQueued循环 AQS->>AQS: tryAcquire(1): state 0->1 Note over AQS: state=1, owner=T2 AQS-->>T2: 获取成功

第三章:ReentrantLock 源码深度剖析

1. 类结构设计

1.1 整体结构

ReentrantLock采用组合模式 ,通过内部类Sync及其子类实现锁的核心逻辑:

java 复制代码
public class ReentrantLock implements Lock, java.io.Serializable {
    
    /** 同步器,提供所有实现机制 */
    private final Sync sync;
    
    /** 同步器基类 */
    abstract static class Sync extends AbstractQueuedSynchronizer { ... }
    
    /** 非公平锁实现 */
    static final class NonfairSync extends Sync { ... }
    
    /** 公平锁实现 */
    static final class FairSync extends Sync { ... }
    
    // 构造方法
    public ReentrantLock() {
        sync = new NonfairSync();  // 默认非公平锁
    }
    
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
}

1.2 类图

1.3 设计亮点

  1. 策略模式 :通过Sync子类切换公平/非公平策略
  2. 模板方法 :继承AQS,只重写tryAcquiretryRelease
  3. 组合优于继承:ReentrantLock组合Sync,而非直接继承AQS
  4. 内部类封装:Sync类对外不可见,隐藏实现细节

2. Sync抽象类

2.1 核心方法概览

java 复制代码
abstract static class Sync extends AbstractQueuedSynchronizer {
    
    /** 获取锁,由子类实现不同策略 */
    abstract void lock();
    
    /** 非公平tryAcquire,公平锁和非公平锁的tryLock()都用它 */
    final boolean nonfairTryAcquire(int acquires) { ... }
    
    /** 释放锁,公平和非公平逻辑相同 */
    protected final boolean tryRelease(int releases) { ... }
    
    /** 判断当前线程是否持有锁 */
    protected final boolean isHeldExclusively() { ... }
    
    /** 创建条件变量 */
    final ConditionObject newCondition() { ... }
    
    // 辅助方法
    final Thread getOwner() { ... }
    final int getHoldCount() { ... }
    final boolean isLocked() { ... }
}

2.2 nonfairTryAcquire - 非公平获取锁

这是ReentrantLock的核心方法,实现了非公平的锁获取逻辑。公平锁和非公平锁的tryLock()方法都会调用它:

java 复制代码
/**
 * 非公平地尝试获取锁
 * @param acquires 获取数量(通常为1)
 * @return 是否获取成功
 */
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    
    // ① 锁空闲
    if (c == 0) {
        // 直接CAS尝试获取,不检查队列(非公平)
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // ② 当前线程已持有锁(重入)
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        // 溢出检查(最大重入次数约21亿)
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    
    // ③ 锁被其他线程持有
    return false;
}

执行流程

2.3 tryRelease - 释放锁

公平锁和非公平锁的释放逻辑完全相同:

java 复制代码
/**
 * 尝试释放锁
 * @param releases 释放数量(通常为1)
 * @return 是否完全释放(state变为0)
 */
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    
    // ① 只有锁持有者才能释放
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    
    boolean free = false;
    
    // ② state减到0,完全释放
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    
    // ③ 更新state(无需CAS,因为是独占的)
    setState(c);
    return free;
}

关键点

  • 只有state==0时才真正释放锁(返回true)
  • 重入场景下,每次unlock只将state减1
  • 不需要CAS,因为只有锁持有者才能调用

2.4 isHeldExclusively - 是否独占持有

java 复制代码
protected final boolean isHeldExclusively() {
    return getExclusiveOwnerThread() == Thread.currentThread();
}

这个方法被Condition使用,用于在await()signal()时检查当前线程是否持有锁。

2.5 辅助方法

java 复制代码
// 获取锁的持有者
final Thread getOwner() {
    return getState() == 0 ? null : getExclusiveOwnerThread();
}

// 获取当前线程的重入次数
final int getHoldCount() {
    return isHeldExclusively() ? getState() : 0;
}

// 判断锁是否被任何线程持有
final boolean isLocked() {
    return getState() != 0;
}

// 创建条件变量
final ConditionObject newCondition() {
    return new ConditionObject();
}

3. 非公平锁NonfairSync

3.1 完整源码

java 复制代码
/**
 * 非公平锁的同步器
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * 获取锁
     * 非公平锁的特点:先尝试插队,失败再排队
     */
    final void lock() {
        // ① 直接尝试CAS获取锁(插队)
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // ② 插队失败,走标准获取流程
            acquire(1);
    }

    /**
     * 尝试获取锁(非公平版本)
     */
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

3.2 非公平锁的"插队"机制

非公平锁有两次插队机会

sequenceDiagram participant T as 新线程 participant AQS as AQS participant Q as 等待队列 Note over T: 调用lock() T->>AQS: ① 第一次插队:CAS(0->1) alt CAS成功 AQS-->>T: 获取成功(插队成功) else CAS失败 T->>AQS: acquire(1) T->>AQS: ② 第二次插队:tryAcquire alt tryAcquire成功 AQS-->>T: 获取成功(插队成功) else tryAcquire失败 T->>Q: 入队等待 Note over T: 老实排队 end end

第一次插队 :在lock()方法中直接CAS

java 复制代码
if (compareAndSetState(0, 1))
    setExclusiveOwnerThread(Thread.currentThread());

第二次插队 :在acquire()中调用tryAcquire()

java 复制代码
// AQS.acquire
if (!tryAcquire(arg) && ...)

这就是为什么非公平锁可能导致等待线程"饥饿"------新来的线程可能抢在等待队列前面获取锁。


3.3 非公平锁的饥饿问题

3.3.1 什么是线程饥饿
java 复制代码
/**
 * 非公平锁可能导致线程饥饿
 */
public class ThreadStarvation {
    
    // 非公平锁(默认)
    private final ReentrantLock unfairLock = new ReentrantLock(false);
    
    /**
     * 场景:线程A持有锁很长时间
     * 线程B、C、D...都在等待
     * 线程A释放后,如果新来的线程E抢到了锁
     * B、C、D继续等待...
     * 
     * 极端情况下,B可能一直获取不到锁
     */
    public void potentialStarvation() {
        unfairLock.lock();
        try {
            // 长时间操作
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            unfairLock.unlock();
        }
    }
}
3.3.2 如何解决饥饿问题
java 复制代码
/**
 * 解决饥饿问题的方案
 */
public class StarvationSolution {
    
    // 方案1:使用公平锁
    private final ReentrantLock fairLock = new ReentrantLock(true);
    
    // 方案2:减少锁持有时间
    private final ReentrantLock lock = new ReentrantLock();
    
    public void reduceHoldTime() {
        lock.lock();
        try {
            // 只做必要操作
            quickOperation();
        } finally {
            lock.unlock();
        }
        // 耗时操作放在锁外
        slowOperation();
    }
    
    // 方案3:使用tryLock超时
    public void withTimeout() {
        try {
            if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
                try {
                    // 业务逻辑
                } finally {
                    lock.unlock();
                }
            } else {
                // 超时处理,而不是无限等待
                handleTimeout();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    private void quickOperation() {}
    private void slowOperation() {}
    private void handleTimeout() {}
}
3.3.3 为什么还要使用非公平锁

尽管可能造成饥饿,非公平锁仍是默认选择:

方面 公平锁 非公平锁
线程饥饿 不会 可能
吞吐量 较低 较高
上下文切换
适用场景 顺序重要 性能重要

大多数场景下,饥饿问题发生的概率很低,非公平锁的性能优势更重要。

4. 公平锁FairSync

4.1 完整源码

java 复制代码
/**
 * 公平锁的同步器
 */
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    /**
     * 获取锁
     * 公平锁的特点:不插队,直接走标准流程
     */
    final void lock() {
        acquire(1);  // 没有插队,直接acquire
    }

    /**
     * 尝试获取锁(公平版本)
     * 与非公平版本的唯一区别:检查是否有前驱等待者
     */
    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;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        
        return false;
    }
}

4.2 hasQueuedPredecessors - 检查前驱等待者

这是公平锁的核心方法,判断当前线程是否需要排队:

java 复制代码
/**
 * 查询是否有线程比当前线程等待更久
 * @return true表示有前驱等待者,当前线程需要排队
 */
public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

逻辑解析

返回true的情况(需要排队):

  1. 队列不为空,且有其他线程在等待
  2. 队列正在初始化(head.next为null)

返回false的情况(可以尝试获取):

  1. 队列为空
  2. 当前线程就是第一个等待者

5. 公平锁vs非公平锁对比

5.1 代码差异

java 复制代码
// ============ 非公平锁 ============
static final class NonfairSync extends Sync {
    
    final void lock() {
        // 差异1:直接尝试CAS插队
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        // 差异2:不检查队列,直接尝试获取
        return nonfairTryAcquire(acquires);
    }
}

// ============ 公平锁 ============
static final class FairSync extends Sync {
    
    final void lock() {
        // 差异1:没有插队,直接acquire
        acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        // ...
        if (c == 0) {
            // 差异2:先检查是否有等待者
            if (!hasQueuedPredecessors() && 
                compareAndSetState(0, acquires)) {
                // ...
            }
        }
        // ...
    }
}

5.2 行为对比

方面 非公平锁 公平锁
lock()首次尝试 CAS插队 直接acquire
tryAcquire检查 不检查队列 检查hasQueuedPredecessors
插队机会 2次 0次
饥饿可能
吞吐量
线程切换

5.3 性能差异分析

非公平锁更快的原因

sequenceDiagram participant T1 as 线程1(持有锁) participant T2 as 线程2(等待中) participant T3 as 线程3(新来的) Note over T1,T3: 非公平锁场景 T1->>T1: 释放锁 T1->>T2: unpark(T2) Note over T2: 唤醒中... T3->>T3: lock() - CAS成功 Note over T3: T3插队获取锁 Note over T2: 唤醒后发现锁没了 Note over T2: 继续等待 Note over T1,T3: 总结:T3不需要上下文切换

非公平锁减少的开销

  1. 减少线程切换次数
  2. 避免"刚唤醒就要睡眠"的情况
  3. 提高CPU缓存命中率

公平锁更慢的原因

  1. 每次获取锁都要检查队列
  2. 必须严格按顺序唤醒
  3. 更多的线程切换

5.4 选择建议

使用非公平锁的场景(默认):

  • 大多数业务场景
  • 追求高吞吐量
  • 锁持有时间短

使用公平锁的场景

  • 严格要求按顺序处理
  • 避免线程饥饿
  • 公平性比性能更重要

5.5 tryLock在公平/非公平锁中的行为

5.5.1 关键区别
java 复制代码
/**
 * tryLock()在公平锁和非公平锁中的行为差异
 * 
 * 【重要】tryLock()不遵循公平性!
 */
public class TryLockBehavior {
    
    // 公平锁
    private final ReentrantLock fairLock = new ReentrantLock(true);
    
    public void demonstrateTryLock() {
        
        // lock()方法遵循公平性
        // 公平锁:检查队列,有等待者则不插队
        // 非公平锁:直接尝试获取
        
        // tryLock()方法【不遵循公平性】!
        // 即使是公平锁,tryLock()也会直接尝试获取,不检查队列!
        if (fairLock.tryLock()) {  // 插队获取,不管队列中是否有等待者
            try {
                // ...
            } finally {
                fairLock.unlock();
            }
        }
        
        // 如果需要公平的tryLock,使用带超时的版本:
        // tryLock(0, TimeUnit.SECONDS) 会遵循公平性!
    }
}
5.5.2 源码分析
java 复制代码
// ReentrantLock.tryLock() 源码
public boolean tryLock() {
    return sync.nonfairTryAcquire(1);  // 【注意】调用的是nonfairTryAcquire!
}

// ReentrantLock.tryLock(timeout, unit) 源码
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));  // 这个会检查公平性
}

为什么tryLock()不遵循公平性?

  • 设计意图:tryLock()是"尝试获取",如果要排队那就不是"尝试"了
  • 性能考虑:检查队列需要额外开销
  • 使用场景:通常用于"能获取就获取,不能就算了"的场景

6. 可重入机制详解

6.1 什么是可重入

可重入(Reentrant)是指同一个线程可以多次获取同一把锁,而不会造成死锁。

java 复制代码
/**
 * 可重入演示
 */
public class ReentrantDemo {
    private final ReentrantLock lock = new ReentrantLock();
    
    public void methodA() {
        lock.lock();  // 第一次获取,state = 1
        try {
            System.out.println("methodA");
            methodB();  // 调用methodB,再次获取锁
        } finally {
            lock.unlock();  // state = 0
        }
    }
    
    public void methodB() {
        lock.lock();  // 第二次获取,state = 2(重入!)
        try {
            System.out.println("methodB");
        } finally {
            lock.unlock();  // state = 1
        }
    }
}

6.2 重入实现原理

获取锁时的重入判断

java 复制代码
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    
    if (c == 0) {
        // 锁空闲,首次获取
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 重入判断:当前线程是否是锁持有者
    else if (current == getExclusiveOwnerThread()) {
        // 是,state累加
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);  // 无需CAS,因为是独占的
        return true;
    }
    
    return false;
}

释放锁时的重入处理

java 复制代码
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;  // state减1
    
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    
    boolean free = false;
    // 只有state减到0才真正释放
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;  // 返回是否完全释放
}

6.3 重入过程图解

sequenceDiagram participant T as 线程 participant Lock as ReentrantLock Note over Lock: state=0, owner=null T->>Lock: lock() [第1次] Lock->>Lock: state: 0→1, owner=T Note over Lock: state=1, owner=T T->>Lock: lock() [第2次,重入] Lock->>Lock: state: 1→2 Note over Lock: state=2, owner=T T->>Lock: lock() [第3次,重入] Lock->>Lock: state: 2→3 Note over Lock: state=3, owner=T T->>Lock: unlock() Lock->>Lock: state: 3→2 Note over Lock: state=2, owner=T T->>Lock: unlock() Lock->>Lock: state: 2→1 Note over Lock: state=1, owner=T T->>Lock: unlock() Lock->>Lock: state: 1→0, owner=null Note over Lock: state=0, owner=null
锁完全释放

6.4 为什么需要可重入

如果锁不支持重入,会发生死锁:

java 复制代码
/**
 * 假设锁不可重入
 */
public class NonReentrantProblem {
    private final Object lock = new Object();
    
    public synchronized void methodA() {
        System.out.println("methodA");
        methodB();  // 死锁!当前线程已持有锁,无法再次获取
    }
    
    public synchronized void methodB() {
        System.out.println("methodB");
    }
}

可重入的意义

  1. 支持递归调用
  2. 支持同步方法相互调用
  3. 简化编程模型,避免意外死锁

6.5 最大重入次数

java 复制代码
int nextc = c + acquires;
if (nextc < 0)  // overflow
    throw new Error("Maximum lock count exceeded");

由于state是int类型,最大重入次数约为Integer.MAX_VALUE(约21亿)。实际使用中基本不可能达到这个限制。


7. 辅助方法源码分析

7.1 getHoldCount - 获取重入次数

java 复制代码
/**
 * 获取当前线程持有锁的次数
 */
public int getHoldCount() {
    return sync.getHoldCount();
}

// Sync.getHoldCount
final int getHoldCount() {
    return isHeldExclusively() ? getState() : 0;
}

使用场景:调试和测试

java 复制代码
public void debugMethod() {
    lock.lock();
    try {
        System.out.println("重入次数: " + lock.getHoldCount());  // 1
        
        lock.lock();
        try {
            System.out.println("重入次数: " + lock.getHoldCount());  // 2
        } finally {
            lock.unlock();
        }
        
        System.out.println("重入次数: " + lock.getHoldCount());  // 1
    } finally {
        lock.unlock();
    }
}

7.2 isHeldByCurrentThread - 当前线程是否持有锁

java 复制代码
/**
 * 查询当前线程是否持有锁
 */
public boolean isHeldByCurrentThread() {
    return sync.isHeldExclusively();
}

// Sync.isHeldExclusively
protected final boolean isHeldExclusively() {
    return getExclusiveOwnerThread() == Thread.currentThread();
}

使用场景:断言检查

java 复制代码
public void criticalSection() {
    // 确保进入临界区时持有锁
    assert lock.isHeldByCurrentThread();
    
    // 临界区代码...
}

7.3 isLocked - 锁是否被任何线程持有

java 复制代码
/**
 * 查询锁是否被任何线程持有
 */
public boolean isLocked() {
    return sync.isLocked();
}

// Sync.isLocked
final boolean isLocked() {
    return getState() != 0;
}

注意:这个方法返回的是"快照",返回后状态可能已经改变。主要用于监控。

7.4 getOwner - 获取锁持有者

java 复制代码
/**
 * 获取锁的持有线程
 * @return 持有线程,如果未锁定则返回null
 */
protected Thread getOwner() {
    return sync.getOwner();
}

// Sync.getOwner
final Thread getOwner() {
    return getState() == 0 ? null : getExclusiveOwnerThread();
}

注意:这是protected方法,主要供子类使用。

7.5 hasQueuedThreads/getQueueLength - 队列相关方法

java 复制代码
/**
 * 查询是否有线程在等待获取锁
 */
public final boolean hasQueuedThreads() {
    return sync.hasQueuedThreads();
}

/**
 * 获取等待队列的长度估计值
 */
public final int getQueueLength() {
    return sync.getQueueLength();
}

使用场景:监控和调优

java 复制代码
public void monitorLock() {
    System.out.println("是否有等待线程: " + lock.hasQueuedThreads());
    System.out.println("等待队列长度: " + lock.getQueueLength());
}

7.6 isFair - 是否为公平锁

java 复制代码
/**
 * 判断是否为公平锁
 */
public final boolean isFair() {
    return sync instanceof FairSync;
}

8. ReentrantLock vs ReentrantReadWriteLock

8.1 核心区别

java 复制代码
/**
 * ReentrantLock vs ReentrantReadWriteLock
 */
public class LockComparison {
    
    // ReentrantLock:互斥锁,任何时刻只有一个线程能访问
    private final ReentrantLock mutexLock = new ReentrantLock();
    
    // ReentrantReadWriteLock:读写锁,读操作可并发
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();
    
    private Object data;
    
    // 使用互斥锁:读写都互斥
    public Object readWithMutex() {
        mutexLock.lock();  // 即使是读操作也要独占
        try {
            return data;
        } finally {
            mutexLock.unlock();
        }
    }
    
    // 使用读写锁:读操作可并发
    public Object readWithRwLock() {
        readLock.lock();  // 多个读线程可以同时持有读锁
        try {
            return data;
        } finally {
            readLock.unlock();
        }
    }
    
    public void writeWithRwLock(Object newData) {
        writeLock.lock();  // 写锁是独占的
        try {
            data = newData;
        } finally {
            writeLock.unlock();
        }
    }
}

8.2 锁降级

ReentrantLock不支持锁降级 ,而ReentrantReadWriteLock支持

java 复制代码
/**
 * 锁降级:写锁 -> 读锁
 * 只有ReadWriteLock支持,ReentrantLock不支持
 */
public class LockDowngrade {
    
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();
    
    private volatile boolean cacheValid = false;
    private Object cachedData;
    
    /**
     * 锁降级示例
     */
    public Object processData() {
        readLock.lock();  // 先获取读锁
        try {
            if (!cacheValid) {
                // 缓存无效,需要写入
                readLock.unlock();  // 必须先释放读锁
                writeLock.lock();   // 再获取写锁
                try {
                    // 双重检查
                    if (!cacheValid) {
                        cachedData = loadFromDB();
                        cacheValid = true;
                    }
                    // 【锁降级】在释放写锁前获取读锁
                    readLock.lock();
                } finally {
                    writeLock.unlock();  // 释放写锁,仍持有读锁
                }
            }
            // 此时持有读锁,可以安全读取
            return cachedData;
        } finally {
            readLock.unlock();
        }
    }
    
    private Object loadFromDB() { return new Object(); }
}

为什么需要锁降级?

  • 在更新数据后,仍需要使用该数据
  • 如果直接释放写锁,其他线程可能修改数据
  • 锁降级保证了数据的一致性

为什么ReentrantLock不需要锁降级?

  • ReentrantLock只有一种锁,没有读写之分
  • 可重入特性已经满足了类似需求

9. 锁测试方法详解

9.1 ReentrantLock提供的测试方法

java 复制代码
/**
 * ReentrantLock的锁测试方法
 */
public class LockTestMethods {
    
    private final ReentrantLock lock = new ReentrantLock();
    
    public void demonstrateTestMethods() {
        
        // 1. isLocked() - 锁是否被任何线程持有
        boolean locked = lock.isLocked();
        System.out.println("锁是否被持有: " + locked);
        
        // 2. isHeldByCurrentThread() - 当前线程是否持有锁
        boolean heldByMe = lock.isHeldByCurrentThread();
        System.out.println("当前线程是否持有: " + heldByMe);
        
        // 3. getHoldCount() - 当前线程的重入次数
        int holdCount = lock.getHoldCount();
        System.out.println("重入次数: " + holdCount);
        
        // 4. hasQueuedThreads() - 是否有线程在等待获取锁
        boolean hasWaiters = lock.hasQueuedThreads();
        System.out.println("是否有等待线程: " + hasWaiters);
        
        // 5. getQueueLength() - 等待获取锁的线程数(估计值)
        int queueLength = lock.getQueueLength();
        System.out.println("等待线程数: " + queueLength);
        
        // 6. isFair() - 是否为公平锁
        boolean fair = lock.isFair();
        System.out.println("是否公平锁: " + fair);
        
        // 7. getOwner() - 获取持有锁的线程(protected方法,需要子类访问)
        // Thread owner = lock.getOwner();  // 需要继承ReentrantLock
    }
    
    /**
     * 实际应用:安全地释放锁
     */
    public void safeUnlock() {
        // 只有持有锁的线程才能释放
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
    
    /**
     * 实际应用:检查死锁风险
     */
    public void checkDeadlockRisk() {
        if (lock.isLocked() && !lock.isHeldByCurrentThread()) {
            System.out.println("警告:锁被其他线程持有,可能造成等待");
        }
    }
}

第四章:Condition 条件变量详解

1. Condition接口概述

1.1 什么是Condition

Condition是JDK 1.5引入的条件变量接口,用于实现线程间的等待/通知机制。它必须与Lock配合使用,提供了比Object.wait/notify更强大、更灵活的功能。

java 复制代码
public interface Condition {
    
    /** 等待,响应中断 */
    void await() throws InterruptedException;
    
    /** 等待,不响应中断 */
    void awaitUninterruptibly();
    
    /** 等待指定纳秒 */
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    
    /** 等待指定时间 */
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    
    /** 等待到指定时刻 */
    boolean awaitUntil(Date deadline) throws InterruptedException;
    
    /** 唤醒一个等待线程 */
    void signal();
    
    /** 唤醒所有等待线程 */
    void signalAll();
}

1.2 为什么需要Condition

synchronized配合Object.wait/notify的局限性:

java 复制代码
/**
 * Object.wait/notify的局限性演示
 */
public class ObjectWaitLimitation {
    
    private final Object lock = new Object();
    private boolean hasData = false;
    private boolean hasSpace = true;
    
    /**
     * 问题:生产者和消费者共用一个等待队列
     * notify()无法精确唤醒特定类型的线程
     */
    public void produce() throws InterruptedException {
        synchronized (lock) {
            while (!hasSpace) {
                lock.wait();  // 等待空间
            }
            // 生产数据...
            hasData = true;
            hasSpace = false;
            lock.notifyAll();  // 只能唤醒所有,包括其他生产者
        }
    }
    
    public void consume() throws InterruptedException {
        synchronized (lock) {
            while (!hasData) {
                lock.wait();  // 等待数据
            }
            // 消费数据...
            hasData = false;
            hasSpace = true;
            lock.notifyAll();  // 只能唤醒所有,包括其他消费者
        }
    }
}

Condition的解决方案

java 复制代码
/**
 * Condition支持多个等待队列
 */
public class ConditionSolution {
    
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();  // 数据可用条件
    private final Condition notFull = lock.newCondition();   // 空间可用条件
    
    public void produce() throws InterruptedException {
        lock.lock();
        try {
            while (isFull()) {
                notFull.await();  // 只在"非满"条件上等待
            }
            // 生产数据...
            notEmpty.signal();    // 只唤醒"非空"条件上的消费者
        } finally {
            lock.unlock();
        }
    }
    
    public void consume() throws InterruptedException {
        lock.lock();
        try {
            while (isEmpty()) {
                notEmpty.await();  // 只在"非空"条件上等待
            }
            // 消费数据...
            notFull.signal();      // 只唤醒"非满"条件上的生产者
        } finally {
            lock.unlock();
        }
    }
    
    private boolean isFull() { return false; /* 简化 */ }
    private boolean isEmpty() { return true; /* 简化 */ }
}

2. Condition vs Object监视器方法

2.1 功能对比

功能 Object方法 Condition方法
等待 wait() await()
等待(不响应中断) - awaitUninterruptibly()
超时等待 wait(timeout) await(time, unit)
等待到指定时刻 - awaitUntil(deadline)
唤醒单个 notify() signal()
唤醒所有 notifyAll() signalAll()
等待队列数量 1个 多个
是否可中断 可选

2.2 使用方式对比

java 复制代码
// ========== Object方式 ==========
synchronized (obj) {
    while (条件不满足) {
        obj.wait();
    }
    // 执行操作
    obj.notify();
}

// ========== Condition方式 ==========
lock.lock();
try {
    while (条件不满足) {
        condition.await();
    }
    // 执行操作
    condition.signal();
} finally {
    lock.unlock();
}

2.3 多条件变量的优势


3. ConditionObject源码剖析

3.1 类结构

ConditionObject是AQS的内部类,实现了Condition接口:

java 复制代码
public class ConditionObject implements Condition, java.io.Serializable {
    
    /** 条件队列的首节点 */
    private transient Node firstWaiter;
    
    /** 条件队列的尾节点 */
    private transient Node lastWaiter;
    
    /** 中断模式:退出wait时重新中断 */
    private static final int REINTERRUPT =  1;
    
    /** 中断模式:退出wait时抛出InterruptedException */
    private static final int THROW_IE    = -1;
}

3.2 条件队列结构

条件队列是一个单向链表 ,使用Node的nextWaiter字段链接:

与同步队列的区别

特性 同步队列(AQS) 条件队列(Condition)
链表类型 双向链表 单向链表
链接字段 prev/next nextWaiter
节点状态 多种 只有CONDITION
用途 等待获取锁 等待条件满足

3.3 Node在条件队列中的复用

java 复制代码
static final class Node {
    // 同步队列使用
    volatile Node prev;
    volatile Node next;
    
    // 条件队列使用(复用)
    Node nextWaiter;
    
    // 等待状态
    volatile int waitStatus;
    
    // CONDITION状态,表示在条件队列中
    static final int CONDITION = -2;
}

4. 条件队列与同步队列

在 ReentrantLock + Condition 的语境下,等待通知机制大致分为两层:Condition 条件队列AQS 同步队列

当线程在持有锁的前提下调用 condition.await(),它会先把自己从同步队列移到条件队列,释放掉当前持有的锁,然后通过 LockSupport.park() 阻塞自己。之后,当其它线程在持有同一把锁的情况下调用 condition.signal()signalAll(),被唤醒的节点会从条件队列被转移回同步队列,此时它并没有立刻恢复执行,而是重新去参与 AQS 级别的抢锁,只有当再次获取到锁之后才真正返回 await() 之后的那一行去继续执行。

这样,Condition 保证了"等待条件时先释放锁,避免占着锁睡觉;条件满足时先唤醒,再按锁的排队规则依次继续"的语义,这比直接 Object.wait/notify 要清晰和强大得多,尤其在存在多个条件时。

4.1 两个队列的关系

4.2 节点流转过程

当调用await()signal()时,节点会在两个队列之间流转:

sequenceDiagram participant T as 线程 participant SQ as 同步队列 participant CQ as 条件队列 participant Lock as 锁 Note over T: 持有锁 T->>CQ: await() - 创建节点加入条件队列 T->>Lock: 释放锁 Note over T: 阻塞在条件队列 Note over T: 其他线程signal() CQ->>SQ: 节点从条件队列转移到同步队列 Note over T: 等待获取锁 SQ->>T: 获取到锁 Note over T: await()返回,继续执行

5. await核心流程

5.1 await方法源码

java 复制代码
public final void await() throws InterruptedException {
    // ① 检查中断
    if (Thread.interrupted())
        throw new InterruptedException();
    
    // ② 创建节点加入条件队列
    Node node = addConditionWaiter();
    
    // ③ 完全释放锁(包括重入),保存state
    int savedState = fullyRelease(node);
    
    int interruptMode = 0;
    
    // ④ 循环检查是否在同步队列中
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);  // 阻塞
        
        // 检查中断
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    
    // ⑤ 重新获取锁
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    
    // ⑥ 清理条件队列中的取消节点
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    
    // ⑦ 处理中断
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

5.2 await流程图

5.3 addConditionWaiter - 加入条件队列

java 复制代码
private Node addConditionWaiter() {
    Node t = lastWaiter;
    
    // 清理已取消的节点
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    
    // 创建CONDITION状态的节点
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    
    // 加入队尾
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    
    return node;
}

5.4 fullyRelease - 完全释放锁

java 复制代码
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();  // 保存当前state(重入次数)
        
        // 释放所有重入
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

为什么要完全释放?

因为await时必须让出锁,否则其他线程无法获取锁来调用signal。保存state是为了await返回后恢复原来的重入次数。

5.5 isOnSyncQueue - 检查是否在同步队列

java 复制代码
final boolean isOnSyncQueue(Node node) {
    // CONDITION状态或prev为null,肯定不在同步队列
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    
    // 有next,肯定在同步队列
    if (node.next != null)
        return true;
    
    // 从tail向前搜索
    return findNodeFromTail(node);
}

6. signal核心流程

6.1 signal方法源码

java 复制代码
public final void signal() {
    // ① 检查当前线程是否持有锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    
    // ② 获取条件队列首节点
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

6.2 doSignal - 执行唤醒

java 复制代码
private void doSignal(Node first) {
    do {
        // 移除首节点
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
        
    // 转移节点,如果失败则尝试下一个
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

6.3 transferForSignal - 转移到同步队列

java 复制代码
final boolean transferForSignal(Node node) {
    // ① 将节点状态从CONDITION改为0
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;  // 节点已取消
    
    // ② 加入同步队列尾部
    Node p = enq(node);
    
    // ③ 设置前驱状态为SIGNAL,确保能被唤醒
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);  // 前驱取消或CAS失败,直接唤醒
    
    return true;
}

6.4 signal流程图

6.5 signalAll - 唤醒所有

java 复制代码
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}

private void doSignalAll(Node first) {
    // 清空条件队列
    lastWaiter = firstWaiter = null;
    
    // 逐个转移所有节点
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

7. 有界阻塞队列

7.1 设计思路

使用两个Condition实现生产者-消费者模式:

7.2 完整实现

java 复制代码
/**
 * 基于ReentrantLock和Condition的有界阻塞队列
 * @param <E> 元素类型
 */
public class BoundedBlockingQueue<E> {
    
    /** 底层存储 */
    private final Object[] items;
    
    /** 队列容量 */
    private final int capacity;
    
    /** 当前元素数量 */
    private int count;
    
    /** 下一个put的位置 */
    private int putIndex;
    
    /** 下一个take的位置 */
    private int takeIndex;
    
    /** 主锁 */
    private final ReentrantLock lock;
    
    /** 非空条件:消费者等待 */
    private final Condition notEmpty;
    
    /** 非满条件:生产者等待 */
    private final Condition notFull;
    
    /**
     * 构造方法
     * @param capacity 队列容量
     */
    public BoundedBlockingQueue(int capacity) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        
        this.capacity = capacity;
        this.items = new Object[capacity];
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.notFull = lock.newCondition();
    }
    
    /**
     * 放入元素(阻塞)
     */
    public void put(E element) throws InterruptedException {
        if (element == null)
            throw new NullPointerException();
        
        lock.lockInterruptibly();
        try {
            // 队列满时,等待notFull条件
            while (count == capacity) {
                notFull.await();
            }
            
            // 放入元素
            enqueue(element);
            
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 取出元素(阻塞)
     */
    public E take() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            // 队列空时,等待notEmpty条件
            while (count == 0) {
                notEmpty.await();
            }
            
            // 取出元素
            return dequeue();
            
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 放入元素(超时)
     */
    public boolean offer(E element, long timeout, TimeUnit unit) 
            throws InterruptedException {
        if (element == null)
            throw new NullPointerException();
        
        long nanos = unit.toNanos(timeout);
        
        lock.lockInterruptibly();
        try {
            while (count == capacity) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            
            enqueue(element);
            return true;
            
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 取出元素(超时)
     */
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            
            return dequeue();
            
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 入队(内部方法)
     */
    private void enqueue(E element) {
        items[putIndex] = element;
        if (++putIndex == capacity)
            putIndex = 0;  // 循环数组
        count++;
        
        notEmpty.signal();  // 通知消费者
    }
    
    /**
     * 出队(内部方法)
     */
    @SuppressWarnings("unchecked")
    private E dequeue() {
        E element = (E) items[takeIndex];
        items[takeIndex] = null;  // help GC
        if (++takeIndex == capacity)
            takeIndex = 0;  // 循环数组
        count--;
        
        notFull.signal();  // 通知生产者
        return element;
    }
    
    /**
     * 获取当前元素数量
     */
    public int size() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 判断队列是否为空
     */
    public boolean isEmpty() {
        return size() == 0;
    }
    
    /**
     * 判断队列是否已满
     */
    public boolean isFull() {
        return size() == capacity;
    }
}

7.3 使用示例

java 复制代码
public class BlockingQueueDemo {
    
    public static void main(String[] args) {
        BoundedBlockingQueue<Integer> queue = new BoundedBlockingQueue<>(5);
        
        // 生产者线程
        Thread producer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    queue.put(i);
                    System.out.println("生产: " + i + ", 队列大小: " + queue.size());
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Producer");
        
        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    Integer item = queue.take();
                    System.out.println("消费: " + item + ", 队列大小: " + queue.size());
                    Thread.sleep(200);  // 消费慢于生产
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Consumer");
        
        producer.start();
        consumer.start();
    }
}

7.4 与ArrayBlockingQueue对比

我们实现的BoundedBlockingQueue与JDK的ArrayBlockingQueue设计思路一致:

java 复制代码
// ArrayBlockingQueue源码(简化)
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    
    final Object[] items;
    int takeIndex;
    int putIndex;
    int count;
    
    final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;
    
    // 实现与我们的几乎相同...
}

第五章:ReentrantLock 实践

1. 常见陷阱与解决方案

1.1 陷阱一:忘记释放锁

java 复制代码
/**
 * 错误:没有在finally中释放锁
 */
public void dangerous() {
    lock.lock();
    doSomething();  // 如果这里抛异常,锁永远不会释放!
    lock.unlock();
}

/**
 * 正确:使用try-finally
 */
public void safe() {
    lock.lock();
    try {
        doSomething();
    } finally {
        lock.unlock();
    }
}

1.2 陷阱二:lock()放在try块内

java 复制代码
/**
 * 错误:lock()在try内部
 */
public void wrongPosition() {
    try {
        lock.lock();  // 如果lock()之前就抛异常,finally会调用unlock()
        doSomething();
    } finally {
        lock.unlock();  // 可能抛出IllegalMonitorStateException
    }
}

/**
 * 正确:lock()在try之前
 */
public void correctPosition() {
    lock.lock();  // lock()在try之前
    try {
        doSomething();
    } finally {
        lock.unlock();
    }
}

1.3 陷阱三:重入次数不匹配

java 复制代码
/**
 * 错误:lock和unlock次数不匹配
 */
public void mismatch() {
    lock.lock();
    lock.lock();  // 重入,state = 2
    try {
        doSomething();
    } finally {
        lock.unlock();  // 只释放一次,state = 1,锁未完全释放!
    }
}

/**
 * 正确:每次lock都对应一次unlock
 */
public void match() {
    lock.lock();
    try {
        lock.lock();
        try {
            doSomething();
        } finally {
            lock.unlock();
        }
    } finally {
        lock.unlock();
    }
}

1.4 陷阱四:Condition.await()用if而非while

java 复制代码
/**
 * 错误:使用if检查条件
 */
public void wrongAwait() throws InterruptedException {
    lock.lock();
    try {
        if (!ready) {  // 可能虚假唤醒
            condition.await();
        }
        // ready可能仍为false!
        process();
    } finally {
        lock.unlock();
    }
}

/**
 * 正确:使用while循环
 */
public void correctAwait() throws InterruptedException {
    lock.lock();
    try {
        while (!ready) {  // 循环检查
            condition.await();
        }
        // ready一定为true
        process();
    } finally {
        lock.unlock();
    }
}

虚假唤醒(Spurious Wakeup):线程可能在没有被signal()的情况下醒来,必须用while循环重新检查条件。

1.5 陷阱五:在错误的线程调用signal

java 复制代码
/**
 * 错误:不持有锁时调用signal
 */
public void wrongSignal() {
    // 没有获取锁
    condition.signal();  // 抛出IllegalMonitorStateException
}

/**
 * 正确:必须在持有锁时调用
 */
public void correctSignal() {
    lock.lock();
    try {
        condition.signal();
    } finally {
        lock.unlock();
    }
}

1.6 陷阱六:tryLock返回值未检查

java 复制代码
/**
 * 错误:忽略tryLock返回值
 */
public void ignoreTryLock() {
    lock.tryLock();  // 返回值被忽略
    try {
        doSomething();  // 可能没有持有锁!
    } finally {
        lock.unlock();  // 可能抛出IllegalMonitorStateException
    }
}

/**
 * 正确:检查返回值
 */
public void checkTryLock() {
    if (lock.tryLock()) {
        try {
            doSomething();
        } finally {
            lock.unlock();
        }
    }
}

2. 死锁预防与检测

2.1 死锁的四个必要条件

2.2 死锁示例

java 复制代码
/**
 * 典型死锁场景
 */
public class DeadlockDemo {
    
    private final ReentrantLock lockA = new ReentrantLock();
    private final ReentrantLock lockB = new ReentrantLock();
    
    // 线程1执行
    public void method1() {
        lockA.lock();
        try {
            Thread.sleep(100);  // 模拟耗时
            lockB.lock();  // 等待lockB
            try {
                // ...
            } finally {
                lockB.unlock();
            }
        } finally {
            lockA.unlock();
        }
    }
    
    // 线程2执行
    public void method2() {
        lockB.lock();
        try {
            Thread.sleep(100);  // 模拟耗时
            lockA.lock();  // 等待lockA,形成死锁!
            try {
                // ...
            } finally {
                lockA.unlock();
            }
        } finally {
            lockB.unlock();
        }
    }
}

2.3 死锁预防策略

避免死锁我通常会从"三板斧"入手:统一加锁顺序、及时释放锁、配合超时/中断机制 。首先,多把锁同时使用时应该约定全局的加锁顺序,比如总是先锁 A 再锁 B,所有代码都遵守这个次序,从根源上破坏死锁的"循环等待"条件。其次,务必用 try { ... } finally { lock.unlock(); } 保证锁一定被释放,避免异常导致锁永远不解。再者,在一些复杂场景下,我会使用 tryLock(long timeout, TimeUnit unit)lockInterruptibly():拿不到锁时可以超时退出或者响应中断,从而"自救",而不是无限期等待。此外,线上排查时可以结合 jstackThreadMXBean 的检测方法去发现和定位潜在死锁。

策略一:固定加锁顺序

java 复制代码
/**
 * 按固定顺序获取锁
 */
public class FixedOrder {
    
    private final ReentrantLock lockA = new ReentrantLock();
    private final ReentrantLock lockB = new ReentrantLock();
    
    // 所有方法都按A->B顺序获取锁
    public void method1() {
        lockA.lock();
        try {
            lockB.lock();
            try {
                // ...
            } finally {
                lockB.unlock();
            }
        } finally {
            lockA.unlock();
        }
    }
    
    public void method2() {
        lockA.lock();  // 同样先A后B
        try {
            lockB.lock();
            try {
                // ...
            } finally {
                lockB.unlock();
            }
        } finally {
            lockA.unlock();
        }
    }
}

策略二:使用tryLock超时

java 复制代码
/**
 * 使用tryLock避免死锁
 */
public boolean safeTransfer(Account from, Account to, int amount) {
    long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
    
    while (true) {
        if (from.lock.tryLock()) {
            try {
                if (to.lock.tryLock()) {
                    try {
                        // 转账操作
                        from.balance -= amount;
                        to.balance += amount;
                        return true;
                    } finally {
                        to.lock.unlock();
                    }
                }
            } finally {
                from.lock.unlock();
            }
        }
        
        // 检查超时
        if (System.nanoTime() >= deadline) {
            return false;  // 超时失败
        }
        
        // 短暂休眠后重试
        Thread.yield();
    }
}

策略三:使用lockInterruptibly

java 复制代码
/**
 * 可中断的获取锁,支持取消
 */
public void interruptibleMethod() throws InterruptedException {
    lock.lockInterruptibly();
    try {
        // ...
    } finally {
        lock.unlock();
    }
}

// 检测到死锁时,可以中断线程来打破死锁
thread.interrupt();

2.4 死锁检测工具

使用jstack检测死锁

bash 复制代码
# 获取线程转储
jstack <pid>

编程方式检测

java 复制代码
/**
 * 使用ThreadMXBean检测死锁
 */
public class DeadlockDetector {
    
    private final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    
    public void detectDeadlock() {
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        
        if (deadlockedThreads != null) {
            ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreads);
            
            System.err.println("检测到死锁!涉及的线程:");
            for (ThreadInfo info : threadInfos) {
                System.err.println(info.getThreadName() + 
                    " 等待锁: " + info.getLockName() +
                    " 持有者: " + info.getLockOwnerName());
            }
        }
    }
    
    // 定期检测
    public void startMonitor() {
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(this::detectDeadlock, 10, 10, TimeUnit.SECONDS);
    }
}

3. 性能优化建议

3.1 减小锁粒度

java 复制代码
/**
 * 粗粒度锁:整个方法加锁
 */
public class CoarseGrained {
    private final ReentrantLock lock = new ReentrantLock();
    private Map<String, Object> cache = new HashMap<>();
    
    public Object getData(String key) {
        lock.lock();
        try {
            Object data = cache.get(key);
            if (data == null) {
                data = loadFromDB(key);  // 耗时IO操作也被锁住
                cache.put(key, data);
            }
            return data;
        } finally {
            lock.unlock();
        }
    }
}

/**
 * 细粒度锁:只锁必要部分
 */
public class FineGrained {
    private final ReentrantLock lock = new ReentrantLock();
    private Map<String, Object> cache = new HashMap<>();
    
    public Object getData(String key) {
        // 先检查缓存(需要锁)
        lock.lock();
        try {
            Object data = cache.get(key);
            if (data != null) {
                return data;
            }
        } finally {
            lock.unlock();
        }
        
        // 缓存未命中,加载数据(不需要锁)
        Object data = loadFromDB(key);
        
        // 更新缓存(需要锁)
        lock.lock();
        try {
            cache.putIfAbsent(key, data);
            return cache.get(key);
        } finally {
            lock.unlock();
        }
    }
}

3.2 减少锁持有时间

java 复制代码
/**
 * 长时间持有锁
 */
public void longHold() {
    lock.lock();
    try {
        prepareData();     // 准备数据(不需要锁)
        processData();     // 处理数据(需要锁)
        saveResult();      // 保存结果(不需要锁)
    } finally {
        lock.unlock();
    }
}

/**
 * 只在必要时持有锁
 */
public void shortHold() {
    Object data = prepareData();  // 准备数据(锁外)
    
    lock.lock();
    try {
        processData(data);  // 处理数据(锁内)
    } finally {
        lock.unlock();
    }
    
    saveResult();  // 保存结果(锁外)
}

3.3 使用读写锁

java 复制代码
/**
 * 读多写少场景使用ReadWriteLock
 */
public class ReadWriteOptimization {
    
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
    
    private Map<String, Object> data = new HashMap<>();
    
    // 读操作:多线程可并发
    public Object read(String key) {
        readLock.lock();
        try {
            return data.get(key);
        } finally {
            readLock.unlock();
        }
    }
    
    // 写操作:独占
    public void write(String key, Object value) {
        writeLock.lock();
        try {
            data.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }
}

3.4 避免锁竞争

java 复制代码
/**
 * 使用ThreadLocal避免锁竞争
 */
public class ThreadLocalOptimization {
    
    // 每个线程有自己的SimpleDateFormat,无需同步
    private static final ThreadLocal<SimpleDateFormat> dateFormat = 
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
    
    public String format(Date date) {
        return dateFormat.get().format(date);  // 无锁
    }
}

3.5 公平锁vs非公平锁选择

java 复制代码
/**
 * 性能对比
 */
public class FairnessPerformance {
    
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 10;
        int iterations = 100000;
        
        // 非公平锁测试
        long unfairTime = testLock(new ReentrantLock(false), threadCount, iterations);
        System.out.println("非公平锁耗时: " + unfairTime + "ms");
        
        // 公平锁测试
        long fairTime = testLock(new ReentrantLock(true), threadCount, iterations);
        System.out.println("公平锁耗时: " + fairTime + "ms");
        
        // 通常公平锁会慢很多
    }
    
    private static long testLock(ReentrantLock lock, int threads, int iterations) 
            throws InterruptedException {
        // 测试代码...
        return 0;
    }
}

结论:非公平锁通常比公平锁快很多(可能快10倍以上),除非有特殊公平性要求,否则使用默认的非公平锁。


4. 锁选型指南

4.1 锁机制对比

锁机制 特点 适用场景
synchronized 简单、自动释放、JVM优化 简单同步,低竞争
ReentrantLock 功能丰富、灵活 需要高级特性
ReadWriteLock 读写分离 读多写少
StampedLock 乐观读、高性能 读多写少,追求性能
AtomicXxx 无锁CAS 简单原子操作

4.2 选型决策树

4.3 ReentrantLock vs StampedLock

java 复制代码
/**
 * StampedLock示例(JDK 8+)
 */
public class StampedLockExample {
    
    private final StampedLock sl = new StampedLock();
    private double x, y;
    
    // 写锁
    void move(double deltaX, double deltaY) {
        long stamp = sl.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }
    
    // 乐观读(无锁)
    double distanceFromOrigin() {
        long stamp = sl.tryOptimisticRead();  // 获取乐观读标记
        double currentX = x, currentY = y;
        
        if (!sl.validate(stamp)) {  // 检查是否被写入
            // 乐观读失败,升级为悲观读
            stamp = sl.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                sl.unlockRead(stamp);
            }
        }
        
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

StampedLock的优势

  • 乐观读不阻塞写
  • 读多写少时性能更好

StampedLock的限制

  • 不可重入
  • 不支持Condition
  • 使用更复杂

5. JDK中ReentrantLock的应用

5.1 典型应用场景

java 复制代码
/**
 * JDK中使用ReentrantLock的类
 */

// 1. ArrayBlockingQueue - 有界阻塞队列
public class ArrayBlockingQueue<E> {
    final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;
    // 使用ReentrantLock实现put/take的阻塞等待
}

// 2. LinkedBlockingQueue - 链表阻塞队列
public class LinkedBlockingQueue<E> {
    private final ReentrantLock takeLock = new ReentrantLock();
    private final ReentrantLock putLock = new ReentrantLock();
    // 双锁提高并发度
}

// 3. ThreadPoolExecutor - 线程池
public class ThreadPoolExecutor {
    private final ReentrantLock mainLock = new ReentrantLock();
    // 保护workers集合的访问
}

// 4. CyclicBarrier - 循环屏障
public class CyclicBarrier {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition trip = lock.newCondition();
    // 使用Condition实现等待/唤醒
}

// 5. PrintWriter (某些实现)
// 6. StampedLock (内部使用)

5.2 为什么这些类选择ReentrantLock

选择ReentrantLock的原因
ArrayBlockingQueue 需要多个Condition(notEmpty, notFull)
LinkedBlockingQueue 需要分离的锁提高并发
ThreadPoolExecutor 需要tryLock避免死锁
CyclicBarrier 需要Condition实现等待

6. 序列化与分布式

6.1 ReentrantLock不可序列化

java 复制代码
/**
 * ReentrantLock的序列化问题
 */
public class SerializationIssue {
    
    // ReentrantLock没有实现Serializable接口
    // private final ReentrantLock lock = new ReentrantLock();
    
    /**
     * 如果类需要序列化,锁字段需要特殊处理
     */
    public class SerializableWithLock implements Serializable {
        
        private static final long serialVersionUID = 1L;
        
        // 使用transient,不序列化锁
        private transient ReentrantLock lock = new ReentrantLock();
        
        private int data;
        
        // 反序列化时重新创建锁
        private void readObject(ObjectInputStream in) 
                throws IOException, ClassNotFoundException {
            in.defaultReadObject();
            lock = new ReentrantLock();  // 重新创建
        }
        
        public void increment() {
            lock.lock();
            try {
                data++;
            } finally {
                lock.unlock();
            }
        }
    }
}

为什么ReentrantLock不可序列化?

  1. 锁状态是运行时状态:序列化后再反序列化,原来的锁状态没有意义
  2. 线程绑定:锁与特定的线程关联,序列化后线程已不存在
  3. 设计选择:Doug Lea认为锁不应该被序列化

6.2 分布式环境的线程安全

ReentrantLock只能保证单JVM内的线程安全,分布式环境需要分布式锁:

java 复制代码
/**
 * 分布式环境下的锁方案
 */
public class DistributedLockOptions {
    
    /**
     * 方案1:基于Redis的分布式锁
     */
    public void redisLock() {
        // 使用Redisson
        // RLock lock = redissonClient.getLock("myLock");
        // lock.lock();
        // try { ... } finally { lock.unlock(); }
    }
    
    /**
     * 方案2:基于ZooKeeper的分布式锁
     */
    public void zookeeperLock() {
        // 使用Curator
        // InterProcessMutex lock = new InterProcessMutex(client, "/locks/myLock");
        // lock.acquire();
        // try { ... } finally { lock.release(); }
    }
    
    /**
     * 方案3:基于数据库的分布式锁
     */
    public void databaseLock() {
        // SELECT ... FOR UPDATE
        // 或使用乐观锁(版本号)
    }
}

分布式锁 vs JVM锁

特性 ReentrantLock 分布式锁
作用范围 单JVM 跨JVM/跨机器
性能 纳秒级 毫秒级
可靠性 JVM崩溃锁丢失 可设置过期时间
复杂度 简单 需要额外中间件
可重入 支持 取决于实现

总结

又是没有大厂约面日子😣😣😣,小编还在找实习的路上,这篇文章是我的笔记汇总整理。

相关推荐
bot5556661 小时前
企业微信iPad协议:从接口设计到灰度验证的极简实践
后端
AI大模型1 小时前
2025最新大模型技术学习路线:从入门到精通,一篇文章全掌握
程序员·llm·agent
程序员西西1 小时前
Spring Boot3 分页操作全解析:从基础到实战
java·后端·程序员
用户68545375977691 小时前
为什么Python大神都在用with?看完我悟了
后端
mudtools1 小时前
一分钟实现.NET与飞书长连接的WebSocket架构
后端·c#·.net
Boop_wu1 小时前
[Java EE] 网络原理(1)
java·网络·java-ee
Sunsets_Red1 小时前
二项式定理
java·c++·python·算法·数学建模·c#
Mcband1 小时前
【Spring Boot】Interceptor的原理、配置、顺序控制及与Filter的关键区别
java·spring boot·后端
qq_479875431 小时前
std::true_type {}
java·linux·服务器