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. python的包与模块

    win +R d

  2. Python编译工具Anaconda(含有spyder+jupyter)

    Anaconda的下载和安装 官方的下载地址:https://www.anaconda.com/distribution/ 安装程序为一个可执行程序文件,下载完成后双击执行程序即可完成安装.安装过程一 ...

  3. R 多图间距调整

    在R中多图画到一起的时候,各图间距通常默认的较远. 如下图: 1 par(mfcol=c(2,1)) 2 plot(1:100) 3 plot(1:100) 调整图片间距这时我们要用到par()函数中 ...

  4. selenium+chrome抓取数据,运行js

    某些特殊的网站需要用selenium来抓取数据,比如用js加密的,破解难度大的 selenium支持linux和win,前提是必须安装python3,环境配置好 抓取代码: #!/usr/bin/en ...

  5. Linux实现批量添加用户及随机密码小脚本

    通过chpasswd命令可实现迅速为用户批量设置密码     实例:写一个脚本,实现批量添加20个用户user1-20,密码为用户名和后面跟5个随机字符 #!/bin/sh # 思路:通过for循环, ...

  6. Java中static关键字声明的静态内部类与非静态内部类的区别

    (1)内部静态类不需要有指向外部类的引用.但非静态内部类需要持有对外部类的引用.(2)非静态内部类能够访问外部类的静态和非静态成员.静态类不能访问外部类的非静态成员.他只能访问外部类的静态成员.(3) ...

  7. 日常Java 2021/11/16

    获得applet参数 下面的例子演示了如何使用一个Applet响应来设置文件中指定的参数.该Applet显示了-个黑色棋盘图案和第二种颜色.第二种颜色和每一列的大小通过文档中的Applet的参数指定. ...

  8. 学习java 7.28

    学习内容: Applet Applet一般称为小应用程序,Java Applet就是用Java语言编写的这样的一些小应用程序,它们可以通过嵌入到Web页面或者其他特定的容器中来运行,也可以通过Java ...

  9. 用户体验再升级!Erda 1.2 版本正式发布

    来源|尔达 Erda 公众号 Erda v1.2 Changelog: https://github.com/erda-project/erda/blob/master/CHANGELOG/CHANG ...

  10. A Child's History of England.16

    CHAPTER 5 ENGLAND UNDER CANUTE THE DANE Canute reigned eighteen years. He was a merciless King at fi ...