AQS源码解析

作者: 沐风之境

AQS源码解析

AQS是什么?

全称是AbstractQueuedSynchronizer,位于java.util.concurrent.locks包下面。AbstractQueuedSynchronizer是一个抽象类,其常见的派生子类有,ReentrantLock.Sync内部类。

申请锁入口方法

acquire方法为AQS中用于申请锁定的入口方法

// 先尝试使用去获取锁,如果失败,则尝试加入申请队列
public final void acquire(int arg) {
  // 尝试申请失败,并且加入等待线程队列的后线程状态为中断状态的情况
  // addWaiter也是自旋操作
  // acquireQueued方法中拥有自旋操作
  // 所以 addWaiter 和 acquireQueued只要执行完成说明已经获取到了锁
  if (!tryAcquire(arg) &&
      acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    // 进行自中断
    selfInterrupt();
}

tryAcquire 方法是是AQS中用于尝试申请锁的方法,这里用到的模板方法,如果子类中没有重写改方法,那么直接调用会出错

// 尝试锁定成功会返回true,失败返回返回false
protected boolean tryAcquire(int arg) {
  throw new UnsupportedOperationException();
}

addWaiter()方法会生成一个等待队列的节点,并且会自旋的去加入等待队列的尾部

private Node addWaiter(Node mode) {
  Node node = new Node(mode);

  // 自旋加入
  for (;;) {
    // 获取当前的队尾元素
    Node oldTail = tail;
    // 如果存在队尾元素
    if (oldTail != null) {
      // 将当前节点添加到队尾
      node.setPrevRelaxed(oldTail);
      // 用CAS算法设置当前AQS的队尾元素为当前节点
      if (compareAndSetTail(oldTail, node)) {
        // 当前队尾元素的的后置节点设置为当前节点,进行两个节点的双向绑定
        oldTail.next = node;
        // 返回当前的节点
        return node;
      }
      // 如果不存在队尾元素,就执行队列初始化操作
      initializeSyncQueue();
      // 执行完成后,但下一个for循环进入后,初始化完毕了
    }
  }
}

initializeSyncQueue()初始化等待队列

private final void initializeSyncQueue() {
  Node h;
  // 用CAS算法生成一个头节点
  if (HEAD.compareAndSet(this, null, (h = new Node())))
    // 生成头节点成功后,将头节点也指定为尾节点
    tail = h;
}

acquireQueued()调度申请线程队列的方法

final boolean acquireQueued(final Node node, int arg) {
  boolean interrupted = false;
  try {
    // 自旋处理等待的线程队列
    for (;;) {
      // 取当前队列的前置节点,p
      final Node p = node.predecessor();
      // 如果p是AQS的头节点,那先去尝试获取AQS的锁如果成功
      if (p == head && tryAcquire(arg)) {
        // 设置当前的节点为头节点
        setHead(node);
        // 节点已被使用,将元素指定为null,帮助GC回收内存
        p.next = null; // help GC
        // 返回当前线程的是的中断状态
        return interrupted;
      }
      // 不是头节点或者不能成功申请到锁的情况
      // 当前线程是否需要暂停
      if (shouldParkAfterFailedAcquire(p, node))
        // 等同于 interrupted = interrupted | parkAndCheckInterrupt()
        // 暂停当前线程,并获取当前线程的中断状态
        interrupted |= parkAndCheckInterrupt();
    }
  } catch (Throwable t) {
    cancelAcquire(node);
    if (interrupted)
      selfInterrupt();
    throw t;
  }
}

shouldParkAfterFailedAcquire()判断当前等待的线程节点是否需要阻塞并停止调度,传入当前节点的前置节点和当前节点作为参数

waitStatus的种类

  • 初始化状态:0
  • SIGNAL:-1
  • CANCELLED: 1
  • CONDITION: -2
  • PROPAGATE: -3
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  // 前置的节点的等待状态
  int ws = pred.waitStatus;
  // 如果前置节点为SIGNAL状态,说明当前节点为下一个启动节点,可以暂停当前节点线程的调度
  if (ws == Node.SIGNAL)
    /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
    return true;
  // 如果大于0,说明前置节点已被取消
  if (ws > 0) {
    /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
    // 跳过当前的前置节点,往前寻找下一个不是取消状态的节点
    do {
      // 当前前置节点的前一个节点作为前置节点,并且将其指定为当前节点的前置节点
      node.prev = pred = pred.prev;
    } while (pred.waitStatus > 0);
    // 跳过前置节点后寻找到的非取消节点的后置节点为当前节点
    pred.next = node;
    /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
    // 如果是初始状态没有设置过waitStatus的情况或者是共享节点用到的需要传播状态时
    // 使用CAS算法将前置节点的状态变为SIGNAL状态
    pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
  }
  return false;
}

parkAndCheckInterrupt() 阻塞当前线程并停止其调度,以节约系统的资源。

private final boolean parkAndCheckInterrupt() {
  // 暂停当前线程
  LockSupport.park(this);
  // 返回当前线程的中断状态,true表示当前线程已经被中断
  return Thread.interrupted();
}

原文创作:沐风之境

原文链接:https://www.cnblogs.com/mufeng3421/p/13335659.html

更多推荐

更多
  • Dubbo源码解读与实战-41加餐:一键通关服务发布全流程 41 加餐:一键联 Dubbo 中的这些核心实现,分析 Dubbo**服务发布** 和**服务引用**的全流程,帮助你将之前课时介绍的独立知识点联系起来,形成一个完整整体。 本课时我们就先来重点关注 Provider 节点发布服务的过
  • Dubbo源码解读与实战-42加餐:服务引用流程全解析 42 加餐:已经分析了服务发布的核心流程。那么在本课时,我们就接着深入分析**服务引用的核心流程**。 Dubbo 支持两种方式引用远程的服务: **服务直连的方式** ,仅适合在**调试服务**的时候使用; **基于注册中心引用服
  • Dubbo源码解读与实战-36负载均衡:公平公正物尽其用的负载均衡策略,这里都有(下) 36 负载均衡:公平公正物尽其用的负载均衡策stractLoadBalance 抽象类的内容,还详细介绍了 ConsistentHashLoadBalance 以及 RandomLoadBalance 这两个实现类的核心原理和大致实现。
  • Dubbo源码解读与实战-47配置中心设计与实现:集中化配置and本地化配置,我都要(上) 47 配置中心设计与实现:集中化配置 and 本地化*,在服务自省架构中也依赖配置中心完成 Service ID 与 Service Name 的映射。配置中心在 Dubbo 中主要承担两个职责: 外部化配置; 服务治理,负责服务治
  • Dubbo源码解读与实战-02Dubbo的配置总线:抓住URL,就理解了半个Dubbo 02 Dubbo 的配置总线:抓住 URL,就理解置总线:抓住 URL,就理解了半个 Dubbo 。 在互联网领域,每个信息资源都有统一的且在网上唯一的地址,该地址就叫 URL(Uniform Resource Locator,统一资
  • Dubbo源码解读与实战-08代理模式与常见实现 08 代理模式与常见实现te 等常用的开源框架,都使用到了动态代理机制。当然,Dubbo 中也使用到了动态代理,在后面开发简易版 RPC 框架的时候,我们还会参考 Dubbo 使用动态代理机制来屏蔽底层的网络传输以及服务发现的相关实现。
  • Dubbo源码解读与实战-48配置中心设计与实现:集中化配置and本地化配置,我都要(下) 48 配置中心设计与实现:集中化配置 and 本地化接口以及 DynamicConfiguration 接口的实现,**其中 DynamicConfiguration 接口实现是动态配置中心的基础**。那 Dubbo 中的动态配置中心是
  • Dubbo源码解读与实战-30Filter接口,扩展Dubbo框架的常用手段指北 30 Filter 接口,扩展 DubborWrapper 的具体实现,这里简单回顾一下。在 buildInvokerChain() 方法中,ProtocolFilterWrapper 会加载 Dubbo 以及应用程序提供的 Filte
  • Dubbo源码解读与实战-31加餐:深潜Directory实现,探秘服务目录玄机 31 加餐:深潜 Directory 实现主题是:深潜 Directory 实现,探秘服务目录玄机。 在生产环境中,为了保证服务的可靠性、吞吐量以及容错能力,我们通常会在多个服务器上运行相同的服务端程序,然后以**集群**的形式对外提
  • Dubbo源码解读与实战-10Netty入门,用它做网络编程都说好(下) 10 Netty 入门,用它做网 的设计。在本课时,我们就深入到 Netty 内部,介绍一下 Netty 框架核心组件的功能,并概述它们的实现原理,进一步帮助你了解 Netty 的内核。 这里我们依旧采用之前的思路来介绍 Netty
  • 近期文章

    更多
    文章目录

      推荐作者

      更多