JavaGuide项目解析:深入理解ReentrantLock与AQS原理

JavaGuide项目解析:深入理解ReentrantLock与AQS原理

JavaGuide JavaGuide:这是一份Java学习与面试指南,它涵盖了Java程序员所需要掌握的大部分核心知识。这份指南是一份通俗易懂、风趣幽默的学习资料,内容全面,深受Java学习者的欢迎。 JavaGuide 项目地址: https://gitcode.com/gh_mirrors/ja/JavaGuide

前言

在Java并发编程中,锁机制是保证线程安全的重要手段。ReentrantLock作为Java并发包中提供的可重入锁,相比synchronized关键字提供了更灵活的锁控制能力。本文将深入分析ReentrantLock的实现原理,特别是它与AQS(AbstractQueuedSynchronizer)框架的关系,帮助开发者更好地理解Java并发锁机制。

ReentrantLock核心特性

基本特点

ReentrantLock是Java并发包中提供的可重入互斥锁,具有以下核心特性:

  1. 可重入性:同一个线程可以多次获取同一把锁
  2. 公平性选择:支持公平锁和非公平锁两种模式
  3. 灵活加锁:提供tryLock()、lockInterruptibly()等多种加锁方式
  4. 条件变量:支持多个Condition条件队列

与synchronized对比

| 特性 | ReentrantLock | synchronized | |---------------------|-----------------------------------|----------------------------| | 实现方式 | Java代码实现 | JVM内置实现 | | 锁获取方式 | 显式调用lock()/unlock() | 隐式获取和释放 | | 可中断 | 支持 | 不支持 | | 超时获取 | 支持 | 不支持 | | 公平性 | 可配置 | 非公平 | | 条件队列 | 支持多个 | 单个 | | 性能 | Java 6+优化后性能接近 | 优化后性能良好 |

AQS框架解析

AQS核心思想

AbstractQueuedSynchronizer(AQS)是Java并发包中锁机制的基石框架,其核心思想是:

  1. 状态管理:通过volatile int state表示同步状态
  2. 队列管理:使用CLH队列变体管理等待线程
  3. 模板方法:提供获取/释放锁的模板方法,具体实现由子类完成

AQS数据结构

AQS内部维护了一个双向队列,队列节点Node包含以下重要字段:

static final class Node {
    volatile int waitStatus;  // 节点状态
    volatile Node prev;       // 前驱节点
    volatile Node next;       // 后继节点
    volatile Thread thread;   // 节点关联线程
    Node nextWaiter;          // 条件队列后继节点
}

waitStatus状态值含义:

  • SIGNAL(-1):后继节点需要被唤醒
  • CANCELLED(1):节点因超时或中断被取消
  • CONDITION(-2):节点在条件队列中
  • PROPAGATE(-3):共享模式下传播状态

同步状态State

AQS使用volatile变量state表示同步状态:

private volatile int state;

对于ReentrantLock,state表示:

  • 0:锁未被占用
  • 0:锁被占用,数值表示重入次数

ReentrantLock与AQS的协作

类结构关系

ReentrantLock内部通过Sync类继承AQS:

  • FairSync:公平锁实现
  • NonfairSync:非公平锁实现

加锁流程分析

非公平锁加锁
final void lock() {
    if (compareAndSetState(0, 1))  // 快速尝试获取锁
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);  // 进入AQS获取流程
}
AQS获取流程
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&  // 子类实现的获取方法
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
非公平锁tryAcquire实现
protected final boolean tryAcquire(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()) {  // 重入逻辑
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

线程入队过程

当tryAcquire失败时,线程会被加入等待队列:

  1. 创建节点:通过addWaiter()创建EXCLUSIVE模式节点
  2. 快速入队:尝试快速设置尾节点
  3. 完整入队:通过enq()方法确保节点正确入队
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;
}

队列中线程的阻塞与唤醒

线程入队后会进入acquireQueued方法:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {  // 前驱是头节点且获取成功
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())  // 检查并阻塞线程
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

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 {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  // 设置前驱为SIGNAL
    }
    return false;
}

解锁流程分析

unlock方法入口

public void unlock() {
    sync.release(1);
}

AQS释放流程

public final boolean release(int arg) {
    if (tryRelease(arg)) {  // 子类实现的释放方法
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);  // 唤醒后继节点
        return true;
    }
    return false;
}

ReentrantLock的tryRelease实现

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {  // 完全释放锁
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

唤醒后继节点

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) {  // 后继节点无效时从尾向前查找
        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);  // 唤醒线程
}

公平锁与非公平锁区别

实现差异

公平锁在tryAcquire中增加了hasQueuedPredecessors检查:

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

性能比较

  • 非公平锁:吞吐量更高,但可能导致线程饥饿
  • 公平锁:保证先来先服务,但上下文切换更多

总结

  1. ReentrantLock通过内部Sync类继承AQS实现锁功能
  2. AQS通过state变量和CLH队列管理同步状态
  3. 非公平锁会直接尝试获取锁,失败后才入队
  4. 公平锁严格遵循FIFO原则,先检查队列再获取
  5. 解锁时会唤醒队列中合适的等待线程

理解ReentrantLock和AQS的协作原理,有助于我们更好地使用并发工具,并在需要时实现自定义的同步器。

JavaGuide JavaGuide:这是一份Java学习与面试指南,它涵盖了Java程序员所需要掌握的大部分核心知识。这份指南是一份通俗易懂、风趣幽默的学习资料,内容全面,深受Java学习者的欢迎。 JavaGuide 项目地址: https://gitcode.com/gh_mirrors/ja/JavaGuide

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卫颂耀Armed

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值