【Java】ReentrantLock

13 阅读8分钟

一、ReentrantLock 解决了什么问题

ReentrantLock 是 Java 并发包 (java.util.concurrent.locks) 中提供的可重入互斥锁,主要解决了以下问题:

  1. 更灵活的同步控制:相比 synchronized 提供了更丰富的锁操作
  2. 公平性选择:支持公平锁和非公平锁两种策略
  3. 可中断的锁获取:解决了 synchronized 无法响应中断的问题
  4. 超时获取锁:避免线程无限期等待
  5. 多条件变量:单个锁可以关联多个条件队列

二、核心特性与实现原理

2.1 可重入性实现

  • 内部维护一个计数器记录重入次数
  • 每次 lock() 计数器+1,unlock() 计数器-1
  • 计数器为0时真正释放锁

2.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;
        }
    }
    // ... 重入逻辑
}

// 公平锁尝试获取
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;
        }
    }
    // ... 重入逻辑
}

三、典型应用场景

3.1 需要可中断锁的场景

ReentrantLock lock = new ReentrantLock();

try {
    lock.lockInterruptibly(); // 可响应中断的获取锁
    // 临界区代码
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 恢复中断状态
} finally {
    if(lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

3.2 需要超时控制的场景

if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // 临界区代码
    } finally {
        lock.unlock();
    }
} else {
    // 超时处理逻辑
}

3.3 复杂条件等待的场景

class BoundedBuffer {
    final ReentrantLock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition(); 
    final Condition notEmpty = lock.newCondition();

    void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await(); // 等待非满条件
            // 放入元素
            notEmpty.signal(); // 唤醒非空等待
        } finally {
            lock.unlock();
        }
    }
}

四、与 synchronized 的对比

4.1 功能对比

特性ReentrantLocksynchronized
锁获取方式显式 lock()/unlock()隐式获取/释放
公平性支持公平/非公平策略只有非公平
可中断lockInterruptibly() 支持不支持
超时机制tryLock(timeout) 支持不支持
条件变量支持多个 Condition单一 wait/notify
性能Java 6+ 两者接近Java 6+ 优化后性能相当

4.2 性能对比(不同场景)

  1. 低竞争场景

    • synchronized 有 JVM 优化优势
    • ReentrantLock 稍慢(因需要方法调用)
  2. 高竞争场景

    • ReentrantLock 表现更稳定
    • 特别是使用公平策略时
  3. 复杂同步场景

    • ReentrantLock 的条件变量更高效

五、优缺点分析

5.1 主要优点

  1. 灵活性

    • 可中断的锁获取
    • 超时获取锁
    • 可设置公平性策略
  2. 功能丰富

    • 多条件变量支持
    • 提供锁状态查询方法
    • 可尝试获取锁(tryLock)
  3. 扩展性

    • 适合实现复杂的同步控制
    • 便于集成到更高级的同步工具中

5.2 主要缺点

  1. 使用复杂度

    • 必须显式调用 unlock()(容易遗漏导致死锁)
    • 需要 try-finally 保证锁释放
  2. 内存开销

    • 每个 ReentrantLock 实例占用更多内存
    • 相比 synchronized 有额外对象创建
  3. 开发成本

    • 简单场景下代码比 synchronized 冗长
    • 需要更多并发编程知识才能正确使用

六、最佳实践建议

  1. 基本使用模板
Lock lock = new ReentrantLock();
// ...
lock.lock();
try {
    // 临界区代码
} finally {
    lock.unlock();
}
  1. 选择策略

    • 默认使用非公平锁(吞吐量更高)
    • 只有严格需要 FIFO 顺序时才用公平锁
  2. 避免常见错误

    • 不要忘记在 finally 中释放锁
    • 避免锁泄露(确保所有路径都释放锁)
    • 不要嵌套使用不同 ReentrantLock 实例
  3. 监控工具

    • 使用 ThreadMXBean 检测锁竞争
    • 通过 JConsole 或 VisualVM 监控锁状态

七、内部实现关键点

  1. AQS 集成

    • 继承 AbstractQueuedSynchronizer
    • 使用 state 字段表示锁状态
    • 实现 tryAcquire/tryRelease 方法
  2. 非公平锁实现

final void lock() {
    if (compareAndSetState(0, 1)) // 先尝试直接获取
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1); // 进入AQS队列
}
  1. 条件变量实现

    • 每个 ConditionObject 维护一个等待队列
    • await() 将线程从同步队列移到条件队列
    • signal() 将线程从条件队列移回同步队列

八 、ReentrantLock 如何通知任务队列执行

ReentrantLock 通过 Condition 机制来通知等待中的任务队列执行,这是比 synchronized 的 wait()/notify() 更灵活的通知机制。下面详细解释其工作原理和实现方式:

一、核心组件

1. Condition 对象

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition(); // 创建条件变量

2. 两个关键队列

  • 同步队列:管理所有等待锁的线程(AQS 维护的 CLH 队列)
  • 条件队列:管理等待特定条件的线程(每个 Condition 对象独立维护)

二、通知机制工作流程

1. 等待流程(await)

lock.lock();
try {
    while(!conditionMet) {
        condition.await(); // 线程进入条件队列
    }
    // 条件满足后执行任务
} finally {
    lock.unlock();
}

await() 内部操作

  1. 创建新的 Node 节点加入条件队列
  2. 完全释放锁(state=0)
  3. 阻塞当前线程(LockSupport.park())
  4. 被唤醒后重新竞争锁
  5. 获取锁后从 await() 返回

2. 通知流程(signal/signalAll)

lock.lock();
try {
    // 改变条件状态
    conditionMet = true;
    condition.signal(); // 唤醒一个等待线程
    // 或 condition.signalAll();
} finally {
    lock.unlock();
}

signal() 内部操作

  1. 将条件队列中的第一个节点转移到同步队列
  2. 设置节点状态为 0(初始状态)
  3. 调用 LockSupport.unpark() 唤醒线程

三、底层实现细节

1. 条件队列结构

public class ConditionObject implements Condition {
    private transient Node firstWaiter; // 条件队列头
    private transient Node lastWaiter;  // 条件队列尾
    
    // Node 状态
    static final int CONDITION = -2;
}

2. 关键转移过程(signal)

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first); // 执行信号传递
}

private void doSignal(Node first) {
    do {
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) && // 将节点转移到同步队列
             (first = firstWaiter) != null);
}

final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    Node p = enq(node); // 加入同步队列
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread); // 唤醒线程
    return true;
}

四、典型应用模式

生产者-消费者模型

class TaskQueue {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();
    private final Queue<Task> queue = new ArrayDeque<>();
    private final int capacity;

    public void put(Task task) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                notFull.await(); // 等待队列非满
            }
            queue.add(task);
            notEmpty.signal(); // 通知消费者
        } finally {
            lock.unlock();
        }
    }

    public Task take() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await(); // 等待队列非空
            }
            Task task = queue.remove();
            notFull.signal(); // 通知生产者
            return task;
        } finally {
            lock.unlock();
        }
    }
}

五、与 synchronized 的对比优势

特性ReentrantLock + Conditionsynchronized + wait/notify
多条件支持一个锁可关联多个 Condition每个对象只有一个等待队列
公平性选择支持公平/非公平策略只有非公平
精确通知可指定唤醒某种条件的等待线程只能随机唤醒或唤醒全部
超时等待支持 await(timeout)只支持 wait(timeout)
中断响应支持可中断的等待基础 wait 不可中断

六、注意事项

  1. 必须持有锁

    • await()/signal() 必须在 lock() 和 unlock() 之间调用
    • 否则抛出 IllegalMonitorStateException
  2. 条件检查

    • 必须使用 while 循环检查条件(防止虚假唤醒)
    while (!condition) {
        cond.await();
    }
    
  3. signal 时机

    • 在修改条件状态后再调用 signal()
    • 避免先 signal 后修改条件导致通知丢失
  4. 资源释放

    • await() 会原子性地释放锁
    • 被唤醒后会重新获取锁

ReentrantLock 通过 Condition 机制来通知等待中的任务队列执行,这是比 synchronized 的 wait()/notify() 更灵活的通知机制。下面详细解释其工作原理和实现方式:

一、核心组件

1. Condition 对象

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition(); // 创建条件变量

2. 两个关键队列

  • 同步队列:管理所有等待锁的线程(AQS 维护的 CLH 队列)
  • 条件队列:管理等待特定条件的线程(每个 Condition 对象独立维护)

二、通知机制工作流程

1. 等待流程(await)

lock.lock();
try {
    while(!conditionMet) {
        condition.await(); // 线程进入条件队列
    }
    // 条件满足后执行任务
} finally {
    lock.unlock();
}

await() 内部操作

  1. 创建新的 Node 节点加入条件队列
  2. 完全释放锁(state=0)
  3. 阻塞当前线程(LockSupport.park())
  4. 被唤醒后重新竞争锁
  5. 获取锁后从 await() 返回

2. 通知流程(signal/signalAll)

lock.lock();
try {
    // 改变条件状态
    conditionMet = true;
    condition.signal(); // 唤醒一个等待线程
    // 或 condition.signalAll();
} finally {
    lock.unlock();
}

signal() 内部操作

  1. 将条件队列中的第一个节点转移到同步队列
  2. 设置节点状态为 0(初始状态)
  3. 调用 LockSupport.unpark() 唤醒线程

三、底层实现细节

1. 条件队列结构

public class ConditionObject implements Condition {
    private transient Node firstWaiter; // 条件队列头
    private transient Node lastWaiter;  // 条件队列尾
    
    // Node 状态
    static final int CONDITION = -2;
}

2. 关键转移过程(signal)

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first); // 执行信号传递
}

private void doSignal(Node first) {
    do {
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) && // 将节点转移到同步队列
             (first = firstWaiter) != null);
}

final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    Node p = enq(node); // 加入同步队列
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread); // 唤醒线程
    return true;
}

四、典型应用模式

生产者-消费者模型

class TaskQueue {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();
    private final Queue<Task> queue = new ArrayDeque<>();
    private final int capacity;

    public void put(Task task) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                notFull.await(); // 等待队列非满
            }
            queue.add(task);
            notEmpty.signal(); // 通知消费者
        } finally {
            lock.unlock();
        }
    }

    public Task take() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await(); // 等待队列非空
            }
            Task task = queue.remove();
            notFull.signal(); // 通知生产者
            return task;
        } finally {
            lock.unlock();
        }
    }
}

五、与 synchronized 的对比优势

特性ReentrantLock + Conditionsynchronized + wait/notify
多条件支持一个锁可关联多个 Condition每个对象只有一个等待队列
公平性选择支持公平/非公平策略只有非公平
精确通知可指定唤醒某种条件的等待线程只能随机唤醒或唤醒全部
超时等待支持 await(timeout)只支持 wait(timeout)
中断响应支持可中断的等待基础 wait 不可中断

六、注意事项

  1. 必须持有锁

    • await()/signal() 必须在 lock() 和 unlock() 之间调用
    • 否则抛出 IllegalMonitorStateException
  2. 条件检查

    • 必须使用 while 循环检查条件(防止虚假唤醒)
    while (!condition) {
        cond.await();
    }
    
  3. signal 时机

    • 在修改条件状态后再调用 signal()
    • 避免先 signal 后修改条件导致通知丢失
  4. 资源释放

    • await() 会原子性地释放锁
    • 被唤醒后会重新获取锁
  • ReentrantLock 的条件通知机制提供了比传统 wait/notify 更灵活、更可控的线程间协作方式,特别适合实现复杂的生产者-消费者模式或多条件等待场景。