AQS是什么

AQS= volatile修饰的state变量(同步状态) +FIFO队列(CLH改善版的虚拟双向队列,用于阻塞等待唤醒机制)

队列里维护的Node节点主要包含:等待状态waitStatus,前后指针,等待的线程。

AQS是个抽象队列同步器,是JUC体系中用来构建锁和其他同步器如 ReentrantLock/CountDownLatch/Semphore的基石。AQS内部通过内置的FIFO先进先出的LCH(虚拟双向链表)队列来完成线程排队,并通过volatile 修饰的int类型状态变量来表示持有锁的状态。

简单的说,AQS通过volatile 修饰的int类型状态变量来表示同步状态,加volatial的目的是保证可见性。然后如果状态变量大于等于1是表示资源被占用,这时候抢不到资源的线程就要进入排队等候队列,等待资源的释放,这里面就需要阻塞等待唤醒机制来实现,AQS通过把等待获取资源的线程封装为Node<Thread>节点入队,在资源释放后通过LockSupport.park().unPark()来唤醒线程,通过CAS自旋来进行资源的抢占。

AQS源码解析——以ReentrantLock为例

公平锁与非公平锁

ReentrantLock默认是非公平锁,如果要实现公平锁构造函数中传入true表示创建的是公平锁。

公平锁相较于非公平锁体现在公平锁会先判断队列中是否有等待的线程,有的话优先获取到锁资源。

ReentrantLock 类图

AQS的Node属性含义

AQS结构图

非公平锁加锁过程

以3个线程分别为ABC争抢锁为例。

代码示例

public class ReentrantLockTest {
//模拟银行排队
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
//第一个获取到锁的客户,执行自己的业务60秒
new Thread(() -> {
lock.lock();
try {
System.out.println("A 获取到锁,执行任务------------");
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "A").start();
//第二个客户获取不到锁,阻塞
new Thread(() -> {
lock.lock();
try {
System.out.println("B 获取到锁,执行任务------------");
} finally {
lock.unlock();
}
}, "B").start();
//第三个客户获取不到锁,阻塞
new Thread(() -> {
lock.lock();
try {
System.out.println("C 获取到锁,执行任务------------");
} finally {
lock.unlock();
}
}, "C").start();
}
}
  1. 当A进来加锁时,会进行CAS加锁,加锁成功就会设置exclusiveOwnerThread目前占用锁的线程为自己。
  2. 当B进来加锁时,也会进行CAS尝试加锁,这时候加锁不成功后,或调用尝试获取锁的方法。这个方法里或再判断下这时候锁状态是否为0,也就是锁是否释放了,如果为0则会再进行CAS尝试加锁;如果锁状态不为0表示被占用了,这时候回判断是否目前加锁的线程是不是自己,如果是的话就进入可重入锁的逻辑,对加锁state变量加1,这就是我们加几次锁就要减几次锁的原因。
  3. 如果B尝试加锁失败后,B就会进入等待队列中进行等待,在加入队列的操作中,AQS会把线程B先包装成一个独占锁模式的Node节点,并判断尾结点是否为空,为空的话就要先判断队列是否还未初始化,如果还未初始化,会先创建一个空的哨兵节点(也叫虚节点,主要作用是用来占位),再将线程B的节点与哨兵节点进行双向队列关联,跟在哨兵节点后面,这时候就入队成功了。如果判断尾结点不为空,那就设置当前节点为尾结点,并与之前的尾结点设置关联关系。
  4. 在添加如队列成功后,线程B会调用acquireQueued方法继续尝试,线程B会通过自旋判断自己在队列中的位置,如果线程B的前节点是哨兵节点,那么线程B进行自旋处理,首先会继续CAS尝试加锁,这时候如果还是不成功,就会设置线程B的前缀节点的等待状态从0变成-1,表示等待被唤醒状态。继续进入自旋逻辑,还是会再尝试CAS尝试加锁一次,还是失败就会调用LockSupport.park(this);方法把线程设置为阻塞状态,等待被唤醒。
  5. 当线程A接收完业务后释放锁,释放锁时当判断释放后state的状态为0时,就会把当前锁的状态设置为0,表示锁已经空闲了,并设置exclusiveOwnerThread目前占用锁的线程为null。然后判断头结点是否不为空且头节点的等待状态为-1等待被唤醒,如果是的话就走唤醒逻辑,先把头节点等待状态设置为初始值0,然后判断头结点的后缀节点如不为空的话,就唤醒它。这样子线程B就会被唤醒了。
  6. 线程B被唤醒后就会继续进行自旋CAS尝试获取锁,这时候就能成功获取到了。而获取到锁后state状态继续变成1表示锁被占用,设置exclusiveOwnerThread目前占用锁的线程为线程B。然后把原来线程B的节点设置为头节点,并把B处理为null的哨兵节点,把原来的哨兵节点取消前后指针引用让GC回收掉。

注意

  • 一个线程会尝试抢4次锁才会进入到等待唤醒的阻塞状态中。

AQS为什么必须有哨兵节点——占位的目的

1.如果没有哨兵节点,那么每次执行入队操作,都需要判断head是否为空,如果为空则head=new Node如果不为空则head.next=new Node,而有哨兵节点则可以大胆的head.next=new Node.

2.如果没有哨兵节点,可能存在之前所说的安全性问题,当只有一个节点的时候执行入队方法,无法保证last和head不为空。哪怕执行enqueue入队之前last和head还指向一个节点,可能由于并发性在具体调用enqueue方法操作last的时候head和last共同指向的头节点已经完成出队,此时last和head都为null,所以enqueue方法中的last.next=new node会抛空指针异常,且由于线程并发性的问题,last始终可能随时为空的问题不使用哨兵节点是无法解决的

AQS的原理及源码分析的更多相关文章

  1. 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理

    1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...

  2. (转)ReentrantLock实现原理及源码分析

    背景:ReetrantLock底层是基于AQS实现的(CAS+CHL),有公平和非公平两种区别. 这种底层机制,很有必要通过跟踪源码来进行分析. 参考 ReentrantLock实现原理及源码分析 源 ...

  3. OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波

    http://blog.csdn.net/chenyusiyuan/article/details/8710462 OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 201 ...

  4. ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap实现原理 ConcurrentHashMap源码分析 总结 ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对Ha ...

  5. HashMap和ConcurrentHashMap实现原理及源码分析

    HashMap实现原理及源码分析 哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表, ...

  6. 【转】HashMap实现原理及源码分析

    哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景极其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出 ...

  7. 【OpenCV】SIFT原理与源码分析:DoG尺度空间构造

    原文地址:http://blog.csdn.net/xiaowei_cqu/article/details/8067881 尺度空间理论   自然界中的物体随着观测尺度不同有不同的表现形态.例如我们形 ...

  8. 《深入探索Netty原理及源码分析》文集小结

    <深入探索Netty原理及源码分析>文集小结 https://www.jianshu.com/p/239a196152de

  9. HashMap实现原理及源码分析之JDK8

    继续上回HashMap的学习 HashMap实现原理及源码分析之JDK7 转载 Java8源码-HashMap  基于JDK8的HashMap源码解析  [jdk1.8]HashMap源码分析 一.H ...

随机推荐

  1. fluidity详解

    fluidity详解 1.fluidity编译过程 1.1.femtools库调用方法 编译fluidity/femtools目录下所有文件,打包为libfemtools.a静态库文件: 通过-lfe ...

  2. Spring Cloud中五花八门的分布式组件我到底该怎么学

    分布式架构的演进 在软件行业,一个应用服务随着功能越来越复杂,用户量越来越大,尤其是互联网行业流量爆发式的增长,导致我们需要不断的重构应用的结构来支撑庞大的用户量,最终从一个简单的系统主键演变成了一个 ...

  3. 表格合并单元格【c#】

    gridBranchInfo.DataSource = dtBranchViewList; gridBranchInfo.DataBind(); Random random = new Random( ...

  4. absurd, abundant

    absurd How: absolutely, completely, clearly, faintly, manifestly, obviously, patently, quite, rather ...

  5. acquire, acre, across

    acquire An acquired taste is an appreciation [鉴赏] for something unlikely to be enjoyed by a person w ...

  6. web必知,多终端适配

    导读 移动端适配,是我们在开发中经常会遇到的,这里面可能会遇到非常多的问题: 1px问题 UI图完美适配方案 iPhoneX适配方案 横屏适配 高清屏图片模糊问题 ... 上面这些问题可能我们在开发中 ...

  7. C++ 数字分类

           1012 数字分类 (20分) 输入格式: 每个输入包含 1 个测试用例.每个测试用例先给出一个不超过 1000 的正整数 N,随后给出 N 个不超过 1000 的待分类的正整数.数字间 ...

  8. adb命令对app进行测试

    1.何为adb adb android  debug  bridge ,sdk包中的工具,将Platform-tooks 和tools  两个路径配置到环境变量中 2.SDK下载链接:http://t ...

  9. SpringBoot(2):运行原理

    一. pom.xml 进入父项目,这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心:以后我们导入依赖默认是不需要写版本:但是如果导入的包没有在依赖中管 ...

  10. Linux:sqlplus

    [oracle@hb shell_test]$ cat echo_time #!/bin/sh 一.最简单的调用sqlplus sqlplus -S "sys/unimas as sysdb ...