J.U.C Review - AQS核心方法解析

文章目录


AQS简介

AQSAbstractQueuedSynchronizer)是一个用来构建锁和同步器的框架。

  • 抽象:抽象类,只实现一些主要逻辑,有些方法由子类实现;
  • 队列:使用先进先出(FIFO)队列存储数据;
  • 同步:实现了同步的功能。

它是一个抽象类,提供了构建同步器的基础功能,子类可以通过实现一些关键的protected方法来创建自定义的同步器。

AQS 主要用于实现各种同步器,如:

  • ReentrantLock
  • Semaphore
  • ReentrantReadWriteLock
  • SynchronousQueue
  • FutureTask

通过AQS,开发者能够高效地构建各种同步器,满足不同的需求。


AQS的数据结构

AQS的核心数据结构包括:

state变量

  1. state变量 :用于标识资源的状态。它是一个volatile类型的整型变量。AQS通过以下方法来操作state变量:

    java 复制代码
    getState()
    setState()
    compareAndSetState()

这些方法都是原子操作,其中compareAndSetState使用了UnsafecompareAndSwapInt()方法来保证操作的原子性。

等待队列

  1. 等待队列 :AQS使用一个FIFO队列来管理线程的排队和阻塞。这个队列实际上存储的是Node节点,而不是线程对象。每个Node节点包含以下信息:
java 复制代码
   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的值,表示有资源可用,新head结点需要继续唤醒后继结点(共享模式下,多线程并发释放资源,而head唤醒其后继结点后,需要把多出来的资源留给后面的结点;设置新的head结点时,会继续唤醒其后继结点)*/
    static final int PROPAGATE = -3;

    // 等待状态,取值范围,-3,-2,-1,0,1
    volatile int waitStatus;
    volatile Node prev; // 前驱结点
    volatile Node next; // 后继结点
    volatile Thread thread; // 结点对应的线程
    Node nextWaiter; // 等待队列里下一个等待条件的结点

    
    // 判断共享模式的方法
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    
    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
    
    // 其它方法忽略,可以参考具体的源码
}

// AQS里面的addWaiter私有方法
private Node addWaiter(Node mode) {
    // 使用了Node的这个构造函数
    Node node = new Node(Thread.currentThread(), mode);
    // 其它代码省略
}
  • SHAREDEXCLUSIVE:分别表示共享模式和独占模式的标记。
  • waitStatus :表示节点的等待状态。它可以是CANCELLED(已取消)、SIGNAL(需要唤醒后继节点)、CONDITION(等待条件)或PROPAGATE(需要继续唤醒后继节点)。

Node节点通过prevnext实现双向队列,支持线程的排队。而通过nextWaiter实现条件队列,主要用于Condition的等待线程。


资源共享模式

AQS支持两种资源共享模式:

  1. 独占模式(Exclusive)

    • 资源是独占的,一次只能由一个线程获取。例如:ReentrantLock
  2. 共享模式(Share)

    • 资源可以被多个线程同时获取,具体的资源个数可以通过参数指定。例如:SemaphoreCountDownLatch

    子类通常只需实现其中一种模式的逻辑。但也有同步类同时实现两种模式,如 ReadWriteLock


AQS的主要方法源码解析

AQS的设计基于模板方法模式,提供了一些必须由子类实现的方法。这些方法包括:

  • isHeldExclusively():检查当前线程是否独占了资源。这通常与条件变量的使用有关。
  • tryAcquire(int arg):尝试以独占模式获取资源。
  • tryRelease(int arg):尝试释放资源(独占模式)。
  • tryAcquireShared(int arg):尝试以共享模式获取资源。
  • tryReleaseShared(int arg):尝试释放资源(共享模式)。

这些方法虽然是protected的,但AQS本身并不提供具体的实现,而是抛出UnsupportedOperationException。子类需要实现这些方法以定义具体的资源获取和释放逻辑。例如:

java 复制代码
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

获取资源

获取资源的入口方法是acquire(int arg)

java 复制代码
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. tryAcquire(arg):尝试获取资源。如果获取失败,则将当前线程加入等待队列。
  2. addWaiter(Node.EXCLUSIVE) :将线程封装成Node节点并添加到等待队列的尾部。

addWaiter方法源码如下:

java 复制代码
 private Node addWaiter(Node mode) {
    // 生成该线程对应的Node节点
    Node node = new Node(Thread.currentThread(), mode);
    // 将Node插入队列中
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        // 使用CAS尝试,如果成功就返回
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果等待队列为空或者上述CAS失败,再自旋CAS插入
    enq(node);
    return node;
}

// 自旋CAS插入等待队列
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

插入等待队列addWaiter方法通过compareAndSetTailenq方法将节点插入队列尾部。enq方法使用自旋CAS确保线程安全。

获取资源的核心逻辑在acquireQueued方法中:

java 复制代码
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋
        for (;;) {
            final Node p = node.predecessor();
            // 如果node的前驱结点p是head,表示node是第二个结点,就可以尝试去获取资源了
            if (p == head && tryAcquire(arg)) {
                // 拿到资源后,将head指向该结点。
                // 所以head所指的结点,就是当前获取到资源的那个结点或null。
                setHead(node); 
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 如果自己可以休息了,就进入waiting状态,直到被unpark()
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • node.predecessor():获取节点的前驱节点。
  • tryAcquire(arg):尝试获取资源。
  • setHead(node):设置新的头节点。
  • parkAndCheckInterrupt():将当前线程挂起,直到被唤醒。

这里parkAndCheckInterrupt方法内部使用到了LockSupport.park(this),
LockSupport类是Java 6 引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数:
park(boolean isAbsolute, long time):阻塞当前线程

unpark(Thread jthread):使给定的线程停止阻塞


释放资源

释放资源的方法是release(int arg)

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

private void unparkSuccessor(Node node) {
    // 如果状态是负数,尝试把它设置为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 得到头结点的后继结点head.next
    Node s = node.next;
    // 如果这个后继结点为空或者状态大于0
    // 通过前面的定义我们知道,大于0只有一种可能,就是这个结点已被取消
    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);
}
  • tryRelease(arg):尝试释放资源。
  • unparkSuccessor(node):唤醒等待队列中的下一个线程。
相关推荐
niucloud-admin3 分钟前
java服务端——controller控制器
java·开发语言
To Be Clean Coder4 分钟前
【Spring源码】通过 Bean 工厂获取 Bean 的过程
java·后端·spring
Fortunate Chen10 分钟前
类与对象(下)
java·javascript·jvm
程序员水自流11 分钟前
【AI大模型第9集】Function Calling,让AI大模型连接外部世界
java·人工智能·llm
‿hhh14 分钟前
综合交通运行协调与应急指挥平台项目说明
java·ajax·npm·json·需求分析·个人开发·规格说明书
小徐Chao努力14 分钟前
【Langchain4j-Java AI开发】06-工具与函数调用
java·人工智能·python
无心水17 分钟前
【神经风格迁移:全链路压测】33、全链路监控与性能优化最佳实践:Java+Python+AI系统稳定性保障的终极武器
java·python·性能优化
萧曵 丶25 分钟前
Synchronized 详解及 JDK 版本优化
java·多线程·synchronized
夏幻灵40 分钟前
JAVA基础:基本数据类型和引用数据类型
java·开发语言
weixin199701080161 小时前
闲鱼 item_get - 商品详情接口对接全攻略:从入门到精通
java·后端·spring