对 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】题目1179:阶乘 解题报告

    [九度OJ]题目1179:阶乘 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1179 题目描述: 输入n, 求y1=1!+3!+-m ...

  2. 【LeetCode】278. First Bad Version 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 解题方法 二分查找 日期 题目地址:https://leetcode.c ...

  3. 【LeetCode】872. Leaf-Similar Trees 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 中序遍历 先序遍历 后序遍历 日期 题目地址:htt ...

  4. Intriguing Properties of Contrastive Losses

    目录 概 主要内容 广义对比损失 不同的先验 不同的权重比 Feature Suppression DigitOnImageNet dataset RandBit dataset 代码 [Chen T ...

  5. CS5265 demoboard|CS5265测试板电路参考|CS5265 Typec转HDMI 4K60HZ方案

    CS5265是TYPEC转HDMI2.0音视频转换芯片,CS5265符合DP1.4协议,且输出的视频信号是HDMI2.0 即4K60HZ  CS5265集成了DP1.4兼容接收机和HDMI2.0b兼容 ...

  6. 使用 JavaScript 根据消费金额和消费者是否为会员确定折扣,最终核算实际应该支付的金额

    查看本章节 查看作业目录 需求说明: 根据消费金额和消费者是否为会员确定折扣,最终核算实际应该支付的金额 消费金额在 200 元以上的会员折扣是 7.5 折,消费金额没有达到 200 元的会员折扣是 ...

  7. Storm对DRPC权限控制Version1.0.1

    对Storm的DRPC进行权限控制, 并且设计相应的测试验证. 1.集群安装 请参考Storm集群安装Version1.0.1 2.使用DRPC功能 请参考Storm集群使用DRPC功能Version ...

  8. 线性基(Linear Basis)学习笔记

    前言 我看网络上没有什么非常系统的教学,可能是我太菜了吧,现在才学,做个记录给自己看. 简略介绍 一个数集能两两异或,能表出许多新的数. 线性基是一个集合,能够在记录最少的数的基础上,表示出一个等价的 ...

  9. 关于一类容斥原理设计 dp 状态的探讨

    写在前面 为什么要写?因为自己学不明白希望日后能掌握. 大体思路大概是 设计一个容斥的方案,并使其贡献可以便于计算. 得出 dp 状态,然后优化以得出答案. 下列所有类似 \([l,r]\) 这样的都 ...

  10. Jenkins_创建git任务(3)

    jenkins创建git任务,需要使用插件 点击Manage Jenkins,点击Manage Plugins 点击Available搜索git,安装git plugin 进入项目管理界面,会有个Gi ...