对 AQS(AbstractQueuedSynchronizer)的理解

Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. This class is designed to be a useful basis for most kinds of synchronizers that rely on a single atomic int value to represent state. Subclasses must define the protected methods that change this state, and which define what that state means in terms of this object being acquired or released. Given these, the other methods in this class carry out all queuing and blocking mechanics. Subclasses can maintain other state fields, but only the atomically updated int value manipulated using methods getState, setState and compareAndSetState is tracked with respect to synchronization.

Subclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class. Class AbstractQueuedSynchronizer does not implement any synchronization interface. Instead it defines methods such as acquireInterruptibly that can be invoked as appropriate by concrete locks and related synchronizers to implement their public methods.

To use this class as the basis of a synchronizer, redefine the following methods, as applicable, by inspecting and/or modifying the synchronization state using getState, setState and/or compareAndSetState:

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

Each of these methods by default throws UnsupportedOperationException. Implementations of these methods must be internally thread-safe, and should in general be short and not block. Defining these methods is the only supported means of using this class. All other methods are declared final because they cannot be independently varied.

For fair locks, it will call hasQueuedPredecessors() to check if there is a queued thread preceding the current thread. If yes, the tryAcquireShared() methods will return -1 immediately.

AQS是 AbstractQueuedSynchronizer 的缩写, 在同步组件的实现中, AQS是核心, 同步组件通过使用AQS提供的模板方法实现同步组件语义, AQS则实现了对同步状态的管理, 以及对阻塞线程进行排队, 等待通知等等一些底层的实现处理. AQS的核心也包括了这些方面: 同步队列, 独占式锁的获取和释放, 共享锁的获取和释放以及可中断锁, 超时等待锁获取这些特性的实现. 这些实际上则是AQS提供的模板方法.

AQS是用来构建锁和其他同步组件的基础框架, 它的实现主要依赖一个int成员变量来表示同步状态以及通过一个FIFO队列构成等待队列, 它的子类必须重写 AQS 的几个 protected 修饰的用来改变同步状态的方法, 其他方法主要是实现了排队和阻塞机制. 状态的更新使用getState, setState以及compareAndSetState这三个方法.

AQS内部实现了两个队列的抽象类: 同步队列和条件队列. 其中同步队列是一个双向链表, 里面储存的是处于等待状态的线程, 排队等待唤醒去获取锁; 而条件队列是一个单向链表, 里面储存的也是处于等待状态的线程, 只不过这些线程唤醒的结果是加入到了同步队列的队尾. AQS所做的就是管理这两个队列里面线程之间的等待状态-唤醒的工作.

在同步队列中还存在2种模式, 分别是独占模式和共享模式, 这两种模式的区别就在于AQS在唤醒线程节点的时候是不是传递唤醒, 这两种模式分别对应独占锁和共享锁.

AQS是一个抽象类, 不能直接实例化, 当我们需要实现一个自定义锁的时候可以去继承AQS然后重写获取锁的方式和释放锁的方式还有管理state.

独占式锁

  • void acquire(int arg): 独占式获取同步状态, 如果获取失败则插入同步队列进行等待;
  • void acquireInterruptibly(int arg): 与acquire方法相同, 但在同步队列中进行等待的时候可以检测中断
  • boolean tryAcquireNanos(int arg, long nanosTimeout): 在acquireInterruptibly基础上增加了超时等待功能, 在超时时间内没有获得同步状态返回false;
  • boolean release(int arg): 释放同步状态, 该方法会唤醒在同步队列中的下一个节点

共享式锁

  • void acquireShared(int arg): 共享式获取同步状态, 与独占式的区别在于同一时刻有多个线程获取同步状态
  • void acquireSharedInterruptibly(int arg): 在acquireShared方法基础上增加了能响应中断的功能;
  • boolean tryAcquireSharedNanos(int arg, long nanosTimeout): 在acquireSharedInterruptibly基础上增加了超时等待的功能;
  • boolean releaseShared(int arg): 共享式释放同步状态

当共享资源被某个线程占有, 其他请求该资源的线程将会阻塞, 从而进入同步队列. AQS中的同步队列则是通过链式方式进行实现. 同步队列是一个双向队列, AQS通过持有头尾指针管理同步队列.

在AQS有一个静态内部类Node, 其中有这样一些属性:

volatile int waitStatus //节点状态
volatile Node prev //当前节点/线程的前驱节点
volatile Node next; //当前节点/线程的后继节点
volatile Thread thread;//加入同步队列的线程引用
Node nextWaiter;//等待队列中的下一个节点

节点的状态有以下这些:

int CANCELLED =  1//节点从同步队列中取消
int SIGNAL = -1//后继节点的线程处于等待状态, 如果当前节点释放同步状态会通知后继节点, 使得后继节点的线程能够运行;
int CONDITION = -2//当前节点进入等待队列中
int PROPAGATE = -3//表示下一次共享式同步状态获取将会无条件传播下去
int INITIAL = 0;//初始状态

当线程获取独占式锁失败后就会将当前线程加入同步队列, 在当前线程是第一个加入同步队列时, 调用compareAndSetHead(new Node())方法, 完成链式队列的头结点的初始化; 如果不是, 则调用compareAndSetTail(pred, node); 以上操作, 如果失败则自旋不断尝试CAS尾插入节点直至成功为止.

获得独占式锁时, 首先获取当前节点的先驱节点, 如果先驱节点是头结点的并且成功获得同步状态时if (p == head && tryAcquire(arg)), 当前节点所指向的线程能够获取锁. 反之, 获取锁失败进入等待状态.

  • 线程获取锁失败, 线程被封装成Node进行入队操作, 核心方法在于addWaiter()和enq(), 同时enq()完成对同步队列的头结点初始化工作以及CAS操作失败的重试;
  • 线程获取锁是一个自旋的过程, 当且仅当 当前节点的前驱节点是头结点并且成功获得同步状态时, 节点出队即该节点引用的线程获得锁, 否则, 当不满足条件时就会调用LockSupport.park()方法使得线程阻塞;
  • 释放锁的时候会唤醒后继节点;

总体来说: 在获取锁时, AQS维护一个同步队列, 获取锁失败的线程会加入到队列中进行自旋;移除队列(或停止自旋)的条件是前驱节点是头结点并且成功获得了锁. 在释放锁时, 同步器会调用unparkSuccessor()方法唤醒后继节点.

参考

Java多线程专题4: 锁的实现基础 AQS的更多相关文章

  1. Java多线程专题1: 并发与并行的基础概念

    合集目录 Java多线程专题1: 并发与并行的基础概念 什么是多线程并发和并行? 并发: Concurrency 特指单核可以处理多任务, 这种机制主要实现于操作系统层面, 用于充分利用单CPU的性能 ...

  2. Java多线程专题5: JUC, 锁

    合集目录 Java多线程专题5: JUC, 锁 什么是可重入锁.公平锁.非公平锁.独占锁.共享锁 可重入锁 ReentrantLock A ReentrantLock is owned by the ...

  3. Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

    本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...

  4. Java多线程系列--“JUC锁”01之 框架

    本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--“JUC锁”01之 框架02. Java多线程系列--“JUC锁”02之 互斥锁Reentrant ...

  5. Java多线程系列--“JUC锁”06之 Condition条件

    概要 前面对JUC包中的锁的原理进行了介绍,本章会JUC中对与锁经常配合使用的Condition进行介绍,内容包括:Condition介绍Condition函数列表Condition示例转载请注明出处 ...

  6. Java多线程并发08——锁在Java中的应用

    前两篇文章中,为各位带来了,锁的类型及锁在Java中的实现.接下来本文将为各位带来锁在Java中的应用相关知识.关注我的公众号「Java面典」了解更多 Java 相关知识点. 锁在Java中主要应用还 ...

  7. Java多线程专题2: JMM(Java内存模型)

    合集目录 Java多线程专题2: JMM(Java内存模型) Java中Synchronized关键字的内存语义是什么? If two or more threads share an object, ...

  8. Java多线程专题6: Queue和List

    合集目录 Java多线程专题6: Queue和List CopyOnWriteArrayList 如何通过写时拷贝实现并发安全的 List? CopyOnWrite(COW), 是计算机程序设计领域中 ...

  9. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

随机推荐

  1. 【九度OJ】题目1109:连通图 解题报告

    [九度OJ]题目1109:连通图 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1109 题目描述: 给定一个无向图和其中的 ...

  2. 【剑指Offer】二叉树的深度 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 解题方法 日期 题目地址:https://www.nowcoder.co ...

  3. LeetCode 1482. 制作 m 束花所需的最少天数

    LeetCode 1482. 制作 m 束花所需的最少天数 题目 给你一个整数数组 bloomDay,以及两个整数 m 和 k . 现需要制作 m 束花.制作花束时,需要使用花园中 相邻的 k 朵花 ...

  4. Polyomino Composer(UVA12291)

    Description   Polyomino Composer  A polyomino is a plane geometric figure formed by joining one or m ...

  5. API 网关功能

    反向代理和路由 - 大多数项目采用网关的解决方案的最主要的原因.给出了访问后端 API 的所有客户端的单一入口,并隐藏内部服务部署的细节. 负载均衡 - 网关可以将单个传入的请求路由到多个后端目的地. ...

  6. CS5268 Typec转HDMI+VGA+PD3.0四合一扩展坞转换器方案芯片

    Capstone CS5268AN是一款高性能Type-C/DP1.4至HDMI2.0b和VGA转换器,设计用于将USB Type-C源或DP1.4源连接至HDMI2.0b接收器.CS5268AN集成 ...

  7. Log4j2基本使用入门

    1.Log4j2简介 Apache Log4j 2是日志框架Log4j的升级, 它比其前身Log4j 1.x提供了重要的改进, 并且参考了Logback中许多有用的改进, 同时修复了Logback的一 ...

  8. CAS学习笔记三:SpringBoot自动配置与手动配置过滤器方式集成CAS客户端

    本文目标 基于SpringBoot + Maven 分别使用自动配置与手动配置过滤器方式集成CAS客户端. 需要提前搭建 CAS 服务端,参考 https://www.cnblogs.com/hell ...

  9. 计算机网络-4-11-IP多播

    IP多播 IP多播的基本概念 与单播相比,在一对多的通信中,多播可以大大减少网络资源.在互联网上进行多播就叫做IP多播,IP多播所传送的分组需要使用多播IP地址.能够运行多播协议的路由器叫做多播路由器 ...

  10. java list 类型删除其中的某些元素的正确方法

    List<Object> list= new ArrayList<>();//记录需要删除的元素List<Object> li = new ArrayList< ...