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. 睡眠或者重启windows,无法ssh连接或者pingVMware的虚机

    睡眠后无法直接ssh重连VMware主机问题 这个问题我在win8上出现过,win10看了下同事可以正常连,不知道其他有没有问题. 解决方法: 1.关闭vmnet8和vmnet0里面的 npcap 功 ...

  2. Telink BLE MESH PWM波的小结

    本本针对Telink BLE MESH SDK  灯控的使用进行说明. 1.调整灯光的频率 默认情况下 SDK PWM波的频率是 600HZ的,有时我们需要将它调整频率,例如调整为4K,只需要更改参数 ...

  3. 28-Merge Two Sorted Lists

    easy 21. Merge Two Sorted Lists Merge two sorted linked lists and return it as a new list. The new l ...

  4. 深入了解scanf() getchar()和gets()等函数之间的区别

    scanf(), getchar()等都是标准输入函数,一般人都会觉得这几个函数非常简单,没什么特殊的.但是有时候却就是因为使用这些函数除了问题,却找不出其中的原因.下面先看一个很简单的程序: 程序1 ...

  5. 一个专业处理字符串的IDEA插件

    字符串处理想必是小伙伴们平时开发时经常碰到的一个 "难题".为什么要打上引号?因为你说他难吧,其实也不是什么特别复杂的事:你说他不难吧,弄起来还真挺麻烦的,像删除其中空行啊.切换大 ...

  6. JavaScript中var与let的异同点

    var是JavaScript刚出现时就存在的变量声明关键字,而let作为ES6才出现的变量声明关键字,无疑两者之间存在着很大的区别.那么具体有哪些区别呢? 1.作用域表现形式不同,var是函数作用域, ...

  7. 【Git项目管理】分布式 Git - 分布式工作流程

    分布式 Git - 分布式工作流程 你现在拥有了一个远程 Git 版本库,能为所有开发者共享代码提供服务,在一个本地工作流程下,你也已经熟悉了基本 Git 命令.你现在可以学习如何利用 Git 提供的 ...

  8. OpenStack之八: network服务(端口9696)

    注意此处用的一个网络,暂时不用启动第二个网官网地址 https://docs.openstack.org/neutron/stein/install/controller-install-rdo.ht ...

  9. 一个超好用的 Python 标准库,彻底玩透路径操作

    pathlib 学习 Python 时,尤其是在进行文件操作和数据处理时,经常会处理路径问题.最常用和常见的是 os.path 模块,它将路径当做字符串进行处理,如果使用不当可能导致难以察觉的错误,而 ...

  10. python模块(三)

    hashilib模块 hashilib模块的主要作用是加密,可以将明文数据通过一系列算法转化为秘闻数据. 目的是为了数据的安全. 加密算法包括md系列,sha系列,base系列,hmac系列. 基本使 ...