对 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. Network (poj1144)

    A Telephone Line Company (TLC) is establishing a new telephone cable network. They are connecting se ...

  2. Mysql客户端的安装

    Mysql数据库(简称)属于C/S架构,正常工作中一般都会提供服务端,我们只需要安装客户端进行查询修改数据等操作即可. 正常工作中不管是测试人员或者开发人员,一般数据库的管理员(测试负责人或者开发负责 ...

  3. <数据结构>XDOJ317.输出完全二叉树的某一层

    问题与解答 问题描述 对一棵完全二叉树,输出某一深度的所有节点,有则输出这些节点,无则输出EMPTY. 输入格式 输入有多组数据. 每组数据第一行输入一个结点数n(1<=n<=1000), ...

  4. 美和易思 MOOT去鼠标检测,快进,倍速,自动下一章

    F12 放到 console 直接运行即可 或者油猴添加新脚本 核心去除网页绑定焦点事件代码: if (!-[1,] && !window.XMLHttpRequest || navi ...

  5. RSA非对称加密算法实现:Golang

    RSA是1977年由罗纳德·李维斯特(Ron Rivest).阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的.当时他们三人都在麻省理工学院工作.RSA ...

  6. Linux 使用 split 命令分割文件

    使用方法: $ split --help 用法:split [选项]... [输入 [前缀]] 将输入内容拆分为固定大小的片段并输出到"前缀aa"."前缀ab" ...

  7. VoIP语音处理流程和知识点梳理

    做音频软件开发10+年,包括语音通信.语音识别.音乐播放等,大部分时间在做语音通信.做语音通信中又大部分时间在做VoIP语音处理.语音通信是全双工的,既要把自己的语音发送出去让对方听到,又要接收对方的 ...

  8. [ flask ] 解耦models(解决models文件太臃肿的问题)

    问题描述 用博客项目来描述,我们在models中定义了用户表(User).文章表(Post).通知表(Notification).等等.随着我们开发的深入,添加的功能越来越多,到后期models文件会 ...

  9. PPT2010制作图片玻璃磨砂效果

    原文链接: https://www.toutiao.com/i6488928834799272462/ 选择"插入"选项卡,"图像"功能组,"图片&q ...

  10. vue爬坑之路(axios 封装篇)

    第一步还是先下载axios cnpm install axios -S第二步建立一个htttp.js import axios from 'axios'; import { Message } fro ...