对 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. 【LeetCode】365. Water and Jug Problem 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 数学题 相似题目 参考资料 日期 题目地址:http ...

  2. 【LeetCode】890. Find and Replace Pattern 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典+set 单字典 日期 题目地址:https:/ ...

  3. D. Persistent Bookcase(Codeforces Round #368 (Div. 2))

    D. Persistent Bookcase time limit per test 2 seconds memory limit per test 512 megabytes input stand ...

  4. WPF之AvalonEdit实现MVVM双向绑定

    AvalonEdit简介 AvalonEdit是基于WPF开发的代码显示控件,默认支持多种不同语言的关键词高亮,并且可以自定义高亮配置.所以通过AvalonEdit可以快速开发出自己想要的代码编辑器. ...

  5. [opencv]GeneralProcessing_Template_Function

    // // Created by leoxae on 2019-05-08. // #ifndef OPENCVDEMO_UTILS_H #define OPENCVDEMO_UTILS_H #inc ...

  6. MySQL客户端mysql常用命令

    通过MySQL自带的mysql命令行工具, 执行MySQL的相关命令. 1.连接MySQL服务端 mysql -uUserName -pPassword -h HostName_IP -P 3306 ...

  7. centos6.5-搭建LNMP

    安装LNMP 一.安装nginx 1.安装相关组件 yum -y install pcre-devel zlib-devel 2.创建启动用户 useradd -M -s /sbin/nologin ...

  8. js 简单版发布留言 案例

    <!DOCTYPE html>   <html lang="en">   <head>       <meta charset=" ...

  9. django中使用支付宝

    一.注册 https://auth.alipay.com/login/ant_sso_index.htm?goto=https%3A%2F%2Fopenhome.alipay.com%2Fplatfo ...

  10. [ bootstrap ] 图片内容占用padding的范围,如何解决?

    问题描述: 从效果图看到,图片内容占据了padding的范围,怎么解决呢? html代码 <div class="container"> <div class=& ...