深入理解 Java AQS 原理与 ReentrantLock 实现

打印 上一主题 下一主题

主题 1728|帖子 1728|积分 5184

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
目录

一、AQS 简介

AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包(java.util.concurrent)中最核心的底子组件之一,它为 Java 中的大多数同步类(如 ReentrantLock、Semaphore、CountDownLatch 等)提供了一个通用的框架。理解 AQS 的工作原理对于深入掌握 Java 并发编程至关重要。
AQS 的作用是解决同步器的实现问题,它将复杂的同步器实现分解为简单的框架方法,开发者只需要实现少量特定的方法就能快速构建出可靠的同步器。
二、AQS 核心设计

2.1 核心组成部分

AQS 重要由以下部分组成:

  • 同步状态(state):使用 volatile int 类型的变量表示资源的可用状态
  • FIFO 等待队列:使用双向链表实现的队列,用于管理等待获取资源的线程
  • 独占/共享模式:支持独占锁(如 ReentrantLock)和共享锁(如 CountDownLatch)两种模式
  • 条件变量:通过 ConditionObject 类提供条件等待/通知机制,类似于 Object.wait()/notify()
2.2 AQS 的工作原理

AQS 通过模板方法模式,将一些通用的同步操作封装在框架内部,而将特定同步器的特性(如资源是否可获取的判断)交给子类去实现。AQS 提供以下基本操作:

  • 资源获取:线程尝试获取资源,如果获取不到,将被包装成 Node 加入等待队列并被阻塞
  • 资源释放:持有资源的线程释放资源后,会唤醒等待队列中的下一个线程
  • 线程阻塞与唤醒:通过 LockSupport 的 park/unpark 机制实现
2.3 AQS 的关键方法

AQS 定义了一组需要子类实现的方法:

  • tryAcquire(int):尝试以独占模式获取资源
  • tryRelease(int):尝试以独占模式释放资源
  • tryAcquireShared(int):尝试以共享模式获取资源
  • tryReleaseShared(int):尝试以共享模式释放资源
  • isHeldExclusively():判断资源是否被当前线程独占
三、ReentrantLock 与 AQS 的关系

ReentrantLock 是基于 AQS 实现的可重入锁,它通过内部类 Sync(继承自 AQS)来实现锁的基本功能,并通过 FairSync 和 NonfairSync 两个子类分别实现公平锁和非公平锁。
3.1 ReentrantLock 的布局
  1. public class ReentrantLock implements Lock {
  2.     private final Sync sync;
  3.     abstract static class Sync extends AbstractQueuedSynchronizer {
  4.         // 实现锁的基本操作
  5.     }
  6.     // 公平锁实现
  7.     static final class FairSync extends Sync { ... }
  8.     // 非公平锁实现
  9.     static final class NonfairSync extends Sync { ... }
  10. }
复制代码
3.2 ReentrantLock 如何使用 AQS 的 state

ReentrantLock 使用 AQS 的 state 字段来表示锁的持有次数:

  • state = 0:表示锁未被持有
  • state > 0:表示锁被持有,值表示重入次数
四、AQS 关键流程分析

4.1 独占锁的获取流程

当线程调用 ReentrantLock.lock()方法时,实际上会执行以下流程:

  • 首先调用 AQS 的 acquire(1)方法:
  1. public final void acquire(int arg) {
  2.     if (!tryAcquire(arg) &&
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  4.         selfInterrupt();
  5. }
复制代码

  • tryAcquire 尝试获取锁,这是由 ReentrantLock 的 Sync 子类实现的:

    • 如果 state=0,尝试使用 CAS 将 state 设为 1,并设置当前线程为持有锁的线程
    • 如果当前线程已经持有锁,则增加 state 值,实现可重入
    • 其他情况下返回 false

  • 如果 tryAcquire 失败,则调用 addWaiter 将当前线程封装成 Node 添加到等待队列末尾:
  1. private Node addWaiter(Node mode) {
  2.     Node node = new Node(Thread.currentThread(), mode);
  3.     // 尝试快速添加到队列尾部
  4.     Node pred = tail;
  5.     if (pred != null) {
  6.         node.prev = pred;
  7.         if (compareAndSetTail(pred, node)) {
  8.             pred.next = node;
  9.             return node;
  10.         }
  11.     }
  12.     // 快速添加失败,进入完整的入队方法
  13.     enq(node);
  14.     return node;
  15. }
复制代码

  • 然后执行 acquireQueued 方法,让该节点在队列中不断尝试获取锁,直到成功或被中断:
  1. final boolean acquireQueued(final Node node, int arg) {
  2.     boolean failed = true;
  3.     try {
  4.         boolean interrupted = false;
  5.         for (;;) {
  6.             // 获取前驱节点
  7.             final Node p = node.predecessor();
  8.             // 如果前驱是头节点,说明轮到当前节点尝试获取锁
  9.             if (p == head && tryAcquire(arg)) {
  10.                 // 获取成功,把当前节点设为头节点
  11.                 setHead(node);
  12.                 p.next = null; // help GC
  13.                 failed = false;
  14.                 return interrupted;
  15.             }
  16.             // 判断是否应该阻塞当前线程
  17.             if (shouldParkAfterFailedAcquire(p, node) &&
  18.                 parkAndCheckInterrupt())
  19.                 interrupted = true;
  20.         }
  21.     } finally {
  22.         if (failed)
  23.             cancelAcquire(node);
  24.     }
  25. }
复制代码
4.2 独占锁的释放流程

当线程调用 ReentrantLock.unlock()方法时,会执行以下流程:

  • 首先调用 AQS 的 release(1)方法:
  1. public final boolean release(int arg) {
  2.     if (tryRelease(arg)) {
  3.         Node h = head;
  4.         if (h != null && h.waitStatus != 0)
  5.             unparkSuccessor(h);
  6.         return true;
  7.     }
  8.     return false;
  9. }
复制代码

  • tryRelease 尝试释放锁,这是由 ReentrantLock 的 Sync 类实现的:

    • 检查当前线程是否是持有锁的线程
    • 减少 state 值
    • 如果 state 变为 0,清空持有锁的线程,并返回 true

  • 如果 tryRelease 返回 true,表示已完全释放锁,则调用 unparkSuccessor 唤醒等待队列中的下一个线程:
[code]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 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

惊落一身雪

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表