1.5w字ReentrantLock 深度解析

100 阅读52分钟

第一章:ReentrantLock 入门

1. 为什么需要ReentrantLock

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

1.1 synchronized的局限性

synchronized是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完美解决了上述所有问题:

synchronized-vs-reentrantlock.drawio.png

1.3 什么时候选择ReentrantLock

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

2. ReentrantLock核心概念

2.1 什么是ReentrantLock

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

  • 可重入(Reentrant):同一个线程可以多次获取同一把锁,不会造成死锁
  • 互斥(Mutual Exclusion):同一时刻只有一个线程能持有锁
  • 锁(Lock):用于控制对共享资源的访问
/**
 * 可重入特性演示
 */
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支持两种获取锁的策略

fair-vs-nonfair-lock.drawio.png

非公平锁(默认):

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

公平锁

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

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

2.3 类结构概览

reentrantlock-class-diagram.drawio.png


3. 基础API详解

3.1 API总览

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

3.2 lock() - 获取锁

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

/**
 * 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() - 可中断获取锁

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

/**
 * 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

/**
 * 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

/**
 * 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时,锁完全释放,其他等待线程可以竞争获取。

/**
 * 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的重要优势。

/**
 * 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 其他辅助方法

/**
 * 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 场景一:线程安全的计数器

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

/**
 * 线程安全的计数器
 */
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()

/**
 * 可中断的资源池
 */
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)来避免永久阻塞。

/**
 * 安全的银行转账(避免死锁)
 */
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 场景四:公平锁保证顺序

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

/**
 * 公平的打印队列
 * 确保打印任务按提交顺序执行
 */
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 功能对比

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

5.2 性能对比

performance-comparison.drawio.png

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

5.3 代码对比

/**
 * 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 选择建议

lock-selection.drawio.png

选择synchronized的情况

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

选择ReentrantLock的情况

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

5.5 异常处理的关键区别

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

/**
 * 异常处理对比 - 这是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是更好的选择:

/**
 * 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语法简洁,不易出错
需要中断/超时ReentrantLocksynchronized不支持
需要公平锁ReentrantLocksynchronized只有非公平
多个等待条件ReentrantLocksynchronized只有一个wait set
性能敏感+低竞争synchronizedJVM优化更好
遗留代码维护synchronized保持一致性

5.7 锁的优缺点总结

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

锁的优点

  1. 简单直观:比无锁编程容易理解和实现
  2. 功能完整:支持复杂的同步逻辑
  3. 可组合性:多个操作可以原子化执行
// 锁可以保护复杂的复合操作
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. 不可组合:两个线程安全的操作组合后可能不安全
// 不可组合的例子:两个线程安全操作组合后不安全
// 假设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-overview.drawio.png AQS的核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效工作线程,并将共享资源设置为锁定状态;如果被请求的共享资源被占用,就需要一套线程阻塞等待以及被唤醒时锁分配的机制,AQS使用CLH队列锁的变体来实现这一机制。

1.2 模板方法模式

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

// 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-vs-shared.drawio.png 独占模式(Exclusive)

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

共享模式(Shared)

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

2. 核心字段解析

2.1 state字段

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

/**
 * 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含义
ReentrantLock0=未锁定,>0=锁定(值表示重入次数)
ReentrantReadWriteLock高16位=读锁数量,低16位=写锁重入次数
Semaphore可用许可数量
CountDownLatch剩余计数
/**
 * 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分别指向队列的头部和尾部:

/**
 * Head of the wait queue, lazily initialized.
 */
private transient volatile Node head;

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

clh-queue.drawio.png

关键点

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

2.3 exclusiveOwnerThread字段

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

// 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)队列是一种基于链表的自旋锁,每个线程在前驱节点上自旋等待:

classic-clh-queue.drawio.png 经典CLH特点

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

3.2 AQS的CLH变体

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

aqs-clh-variant.drawio.png 主要改进

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

3.3 为什么使用双向链表

单向链表的问题

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

双向链表的优势

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

3.4 自旋锁与AQS的自旋机制

什么是自旋锁?

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

/**
 * 简单自旋锁示例
 */
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并非纯自旋锁,而是采用自旋+阻塞的混合策略:

/**
 * 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唤醒后,再次进入循环尝试
// 为什么要在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变量,会将本地缓存刷新到主内存
/**
 * 锁的内存语义示例
 */
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操作:

// 获取锁时的内存语义
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
可见性✅ 保证✅ 保证
原子性❌ 只保证单个读/写✅ 保证临界区原子性
有序性✅ 禁止重排序✅ 禁止重排序
复合操作❌ 不支持✅ 支持
性能更好(无阻塞)有阻塞开销
// 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结构

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中最复杂的字段,用于表示节点的等待状态:

waitstatus-state-diagram.drawio.png

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

SIGNAL状态的重要性

// 节点入队后,需要将前驱的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 节点模式

// 判断是否为共享模式
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流程概览

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

整体流程如下:

acquire-flow.drawio.png

5.2 tryAcquire - 子类实现

以ReentrantLock的非公平锁为例:

// 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 - 创建节点入队

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 - 排队获取锁

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

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流程图

acquireQueued-flow.drawio.png

5.5 shouldParkAfterFailedAcquire - 是否阻塞

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 - 阻塞线程

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

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

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

6. 独占模式释放锁

6.1 release流程

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

release-flow.drawio.png

6.2 tryRelease - 子类实现

// 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 - 唤醒后继

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指针不是原子操作:

// 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及其子类实现锁的核心逻辑:

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 类图

reentrantlock-detailed-class-diagram.drawio.png

1.3 设计亮点

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

2. Sync抽象类

2.1 核心方法概览

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()方法都会调用它:

/**
 * 非公平地尝试获取锁
 * @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;
}

执行流程

nonfairTryAcquire-flow.drawio.png

2.3 tryRelease - 释放锁

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

/**
 * 尝试释放锁
 * @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 - 是否独占持有

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

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

2.5 辅助方法

// 获取锁的持有者
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 完整源码

/**
 * 非公平锁的同步器
 */
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

if (compareAndSetState(0, 1))
    setExclusiveOwnerThread(Thread.currentThread());

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

// AQS.acquire
if (!tryAcquire(arg) && ...)

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


3.3 非公平锁的饥饿问题

3.3.1 什么是线程饥饿
/**
 * 非公平锁可能导致线程饥饿
 */
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 如何解决饥饿问题
/**
 * 解决饥饿问题的方案
 */
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 完整源码

/**
 * 公平锁的同步器
 */
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 - 检查前驱等待者

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

/**
 * 查询是否有线程比当前线程等待更久
 * @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());
}

逻辑解析

hasQueuedPredecessors-flow.drawio.png

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

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

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

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

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

5.1 代码差异

// ============ 非公平锁 ============
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 选择建议

lock-type-selection.drawio.png

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

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

使用公平锁的场景

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

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

5.5.1 关键区别
/**
 * 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 源码分析
// 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)是指同一个线程可以多次获取同一把锁,而不会造成死锁。

/**
 * 可重入演示
 */
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 重入实现原理

获取锁时的重入判断

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;
}

释放锁时的重入处理

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<br/>锁完全释放

6.4 为什么需要可重入

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

/**
 * 假设锁不可重入
 */
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 最大重入次数

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 - 获取重入次数

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

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

使用场景:调试和测试

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 - 当前线程是否持有锁

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

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

使用场景:断言检查

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

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

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

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

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

7.4 getOwner - 获取锁持有者

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

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

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

7.5 hasQueuedThreads/getQueueLength - 队列相关方法

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

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

使用场景:监控和调优

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

7.6 isFair - 是否为公平锁

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

8. ReentrantLock vs ReentrantReadWriteLock

8.1 核心区别

/**
 * 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支持

/**
 * 锁降级:写锁 -> 读锁
 * 只有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提供的测试方法

/**
 * 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更强大、更灵活的功能。

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的局限性:

/**
 * 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的解决方案

/**
 * 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 使用方式对比

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

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

2.3 多条件变量的优势

condition-advantage.drawio.png


3. ConditionObject源码剖析

3.1 类结构

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

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字段链接:

condition-queue-structure.drawio.png

与同步队列的区别

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

3.3 Node在条件队列中的复用

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 两个队列的关系

sync-condition-queues.drawio.png

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方法源码

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流程图

acquire-flow.drawio.png

5.3 addConditionWaiter - 加入条件队列

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 - 完全释放锁

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 - 检查是否在同步队列

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方法源码

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

6.2 doSignal - 执行唤醒

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 - 转移到同步队列

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流程图

signal-flow.drawio.png

6.5 signalAll - 唤醒所有

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实现生产者-消费者模式:

bounded-queue-design.drawio.png

7.2 完整实现

/**
 * 基于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 使用示例

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设计思路一致:

// 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 陷阱一:忘记释放锁

/**
 * 错误:没有在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块内

/**
 * 错误: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 陷阱三:重入次数不匹配

/**
 * 错误: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

/**
 * 错误:使用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

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

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

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

/**
 * 错误:忽略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 死锁的四个必要条件

deadlock-conditions.drawio.png

2.2 死锁示例

/**
 * 典型死锁场景
 */
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 的检测方法去发现和定位潜在死锁。

策略一:固定加锁顺序

/**
 * 按固定顺序获取锁
 */
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超时

/**
 * 使用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

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

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

2.4 死锁检测工具

使用jstack检测死锁

# 获取线程转储
jstack <pid>

编程方式检测

/**
 * 使用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 减小锁粒度

/**
 * 粗粒度锁:整个方法加锁
 */
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 减少锁持有时间

/**
 * 长时间持有锁
 */
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 使用读写锁

/**
 * 读多写少场景使用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 避免锁竞争

/**
 * 使用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非公平锁选择

/**
 * 性能对比
 */
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 选型决策树

lock-selection-tree.drawio.png

4.3 ReentrantLock vs StampedLock

/**
 * 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 典型应用场景

/**
 * 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不可序列化

/**
 * 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内的线程安全,分布式环境需要分布式锁:

/**
 * 分布式环境下的锁方案
 */
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崩溃锁丢失可设置过期时间
复杂度简单需要额外中间件
可重入支持取决于实现

总结

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