当前位置: 首页 > news >正文

JAVA并发编程高级--深入解析 Java ReentrantLock:非公平锁与公平锁的实现原理

一、ReentrantLock 简介

ReentrantLock 是 Java 并发包 java.util.concurrent.locks 中的一个可重入的互斥锁,提供了比内置锁(synchronized)更灵活的锁操作。它支持公平锁和非公平锁两种模式,并且可以尝试非阻塞地获取锁、尝试获取锁并指定等待时间等操作。

二、公平锁与非公平锁的区别

1. 非公平锁(NonfairSync)

非公平锁允许插队,可能会导致某些线程长期等待。非公平锁的 lock() 方法首先尝试通过 CAS 直接获取锁,如果失败则进入 AQS 的 acquire 方法。

final void lock() {
    if (compareAndSetState(0, 1)) { // 尝试通过 CAS 获取锁
        setExclusiveOwnerThread(Thread.currentThread());
    } else {
        acquire(1); // 如果 CAS 失败,进入 AQS 的 acquire 方法
    }
}

2. 公平锁(FairSync)

公平锁按照 FIFO 顺序获取锁,保证线程的公平性。公平锁的 lock() 方法直接调用 AQS 的 acquire 方法,不会尝试直接获取锁。

final void lock() {
    acquire(1); // 直接进入 AQS 的 acquire 方法
}

三、hasQueuedPredecessors 方法解析

hasQueuedPredecessors 方法是公平锁实现的核心,用于检查队列中是否有等待执行的线程。如果队列中有等待线程,则当前线程不能直接获取锁,必须排队等待。

public final boolean hasQueuedPredecessors() {
    Node t = tail; // 获取尾节点
    Node h = head; // 获取头节点
    Node s;
    return h != t && // 如果头尾不一致,说明队列中有节点
           ((s = h.next) == null || s.thread != Thread.currentThread()); // 检查头节点的下一个节点是否为当前线程
}
  • h != t:判断队列是否为空。如果头尾节点不一致,说明队列中有等待线程。

  • s.thread != Thread.currentThread():检查头节点的下一个节点是否为当前线程。如果不是,说明有其他线程在队列中等待。

四、公平锁与非公平锁的实现细节

1. 公平锁的 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;
        }
    } else if (current == getExclusiveOwnerThread()) { // 可重入逻辑
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

2. 非公平锁的 tryAcquire 方法

非公平锁直接尝试通过 CAS 获取锁,不会检查队列。

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0 && compareAndSetState(0, acquires)) { // 直接尝试获取锁
        setExclusiveOwnerThread(current);
        return true;
    }
    return false;
}

五、lockInterruptibly 方法

lockInterruptibly 方法尝试获取锁,但如果当前线程被中断,则抛出 InterruptedException

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}
  • acquireInterruptibly:AQS 的方法,尝试获取锁。如果线程被中断,则抛出 InterruptedException

使用场景

适用于需要响应中断的场景,例如在任务被取消时能够及时释放资源。

示例代码

ReentrantLock lock = new ReentrantLock();
try {
    lock.lockInterruptibly();
    // 执行需要同步的代码
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 处理中断
} finally {
    lock.unlock(); // 确保释放锁
}

六、tryLock 方法

tryLock 方法尝试获取锁,但不会阻塞。如果锁立即可用,则返回 true,否则返回 false

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}
  • nonfairTryAcquire:尝试获取锁,不会检查队列。

使用场景

适用于需要尝试获取锁但不希望阻塞的场景,例如在高并发环境下避免线程长时间等待。

示例代码

ReentrantLock lock = new ReentrantLock();
if (lock.tryLock()) {
    try {
        // 执行需要同步的代码
    } finally {
        lock.unlock(); // 确保释放锁
    }
} else {
    // 锁不可用,执行其他逻辑
}

tryLock 方法还有一个重载版本,允许指定等待时间:

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
  • tryAcquireNanos:尝试在指定时间内获取锁。如果在指定时间内获取到锁,则返回 true,否则返回 false

示例代码

ReentrantLock lock = new ReentrantLock();
try {
    if (lock.tryLock(1, TimeUnit.SECONDS)) {
        try {
            // 执行需要同步的代码
        } finally {
            lock.unlock(); // 确保释放锁
        }
    } else {
        // 锁不可用,执行其他逻辑
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 处理中断
}

七、unlock 方法

unlock 方法释放锁。如果当前线程不是锁的持有者,则抛出 IllegalMonitorStateException

public void unlock() {
    sync.release(1);
}
  • release:AQS 的方法,尝试释放锁。

使用场景

在使用 locklockInterruptiblytryLock 方法获取锁后,必须在 finally 块中调用 unlock 方法,以确保锁被正确释放,避免死锁。

示例代码

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 执行需要同步的代码
} finally {
    lock.unlock(); // 确保释放锁
}

八、总结

  • 非公平锁:先尝试通过 CAS 抢锁,抢不到再排队。这种方式可能会导致某些线程长期等待,但吞吐量较高。

  • 公平锁:不会尝试直接获取锁,而是先检查队列中是否有等待线程,只有队首线程才允许尝试获取锁。这种方式保证了线程的公平性,但可能会导致吞吐量较低。

在实际使用中,可以根据具体需求选择公平锁或非公平锁。如果对响应时间要求较高,建议使用非公平锁;如果需要保证线程的公平性,建议使用公平锁。

ReentrantLock 提供了多种方法来控制锁的行为,包括尝试获取锁、尝试获取锁并指定超时时间、检查锁的状态等。这些方法的实现都基于 AQS 的核心机制,通过 tryAcquiretryReleaseacquirerelease 等方法来实现锁的获取和释放。

http://www.dtcms.com/a/109027.html

相关文章:

  • 【零基础入门unity游戏开发——2D篇】2D 游戏场景地形编辑器——TileMap的使用介绍
  • 虚拟电商-话费充值业务(六)话费充值业务回调补偿
  • MINIQMT学习课程Day3
  • Enovia许可配置和优化
  • seaweedfs分布式文件系统
  • RAC磁盘头损坏问题处理
  • 特征金字塔网络(FPN)详解
  • 【易订货-注册/登录安全分析报告】
  • Oracle触发器使用(二):伪记录和系统触发器
  • 构建个人专属知识库文件的RAG的大模型应用
  • BUUCTF-web刷题篇(9)
  • idea插件(自用)
  • video标签播放mp4格式视频只有声音没有图像的问题
  • NVIDIA显卡
  • 2.3 路径问题专题:剑指 Offer 47. 礼物的最大价值
  • Apollo配置中心登陆页面添加验证码
  • OpenCV销毁窗口
  • 浅谈软件成分分析 (SCA) 在企业开发安全建设中的落地思路
  • 数据库--SQL
  • Pytorch深度学习框架60天进阶学习计划 - 第34天:自动化模型调优
  • 维拉工时自定义字段:赋能项目数据的深度洞察 | 上新预告
  • React-router v7 第一章(安装)
  • JDBC常用的接口
  • coding ability 展开第八幕(位运算——基础篇)超详细!!!!
  • Spring Boot 集成 Redis 对哈希数据的详细操作示例,涵盖不同结构类型(基础类型、对象、嵌套结构)的完整代码及注释
  • PyQt6实例_A股日数据维护工具_使用
  • OpenCV 引擎:驱动实时应用开发的科技狂飙
  • 操作系统(一):概念及主流系统全分析
  • 大模型学习三:DeepSeek R1蒸馏模型组ollama调用流程
  • Vue2 生命周期