AQS原理

简单介绍

AQS即AbstractQuenedSynchronizer,中文名称抽象队列同步器,定义了一套多线程访问共享资源的同步框架,许多同步类的实现都依赖于它,像ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch等。

实现原理

如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是基于CLH 锁实现的。 CLH 锁其实是对自旋锁的一种改进,是一个虚拟的双向队列,暂时获取不到锁的线程将被加入到该队列中。AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。

也就是说AQS中维护了一个volicate int state(共享资源)和一个FIFO的线程等待队列。

这里volatile 能够保证多线程下的可见性,当state=1 则代表当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被放入一个FIFO的等待队列中,并且会挂起,等待其他获取锁的线程释放锁才能够被唤醒,并且state值可以大于一,代表锁是可重入的。此外,对state的操作都是通过CAS来保证其修改并发性。

以可重入的互斥锁ReentrantLock 为例,它的内部维护了一个state 变量,用来表示锁的占用状态。state 的初始值为 0,表示锁处于未锁定状态。当线程 A 调用 lock() 方法时,会尝试通过 tryAcquire() 方法独占该锁,并让 state 的值加 1。如果成功了,那么线程 A 就获取到了锁。如果失败了,那么线程 A 就会被加入到一个等待队列(CLH 队列)中,直到其他线程释放该锁。假设线程 A 获取锁成功了,释放锁之前,A 线程自己是可以重复获取此锁的)。这就是可重入性的体现:一个线程可以多次获取同一个锁而不会被阻塞。但是,这也意味着,一个线程必须释放与获取的次数相同的锁,才能让 state 的值回到 0,也就是让锁恢复到未锁定状态。只有这样,其他等待的线程才能有机会获取该锁。

节点状态

节点状态waitStatus,需要保证可见性,用volicate实现,waitStatus标识Node节点的状态,共有5种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE、0。

  • CANCELLED(1): 表示节点已取消调度,当timeout或被中断时,会触发变更为此状态,进入此状态的节点将不会再变化。
  • SIGNAL(-1): 表示后继节点在等待当前节点的唤醒。后继节点入队时,会将前继节点的状态更新为SIGNAL。
  • CODINTION(-2): 表示节点等待在Condition上,当其他线程调用了Condition的singal()方法后,Condition状态的节点将从等待队列转移到同步队列中。
  • PROPAGATE(-3): 共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
  • 0:新节点入队时的默认状态。

如何自定义实现AQS

同步器的方法是基于模板方法实现的,我们只需要继承AbstractQueuedSynchronizer并且重写指定方法,然后再使用时将AQS组合在自定义同步组件的视线中,并调用其模板方法,这些模板方法就会自动调用使用者的重写方法。

自定义同步器时需要重写以下方法:

Java 复制代码
//独占方式。尝试获取资源,成功则返回true,失败则返回false。
protected boolean tryAcquire(int)
//独占方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryRelease(int)
//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
protected int tryAcquireShared(int)
//共享方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryReleaseShared(int)
//该线程是否正在独占资源。只有用到condition才需要去实现它。
protected boolean isHeldExclusively()
相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
憨子周1 小时前
2M的带宽怎么怎么设置tcp滑动窗口以及连接池
java·网络·网络协议·tcp/ip
霖雨2 小时前
使用Visual Studio Code 快速新建Net项目
java·ide·windows·vscode·编辑器
SRY122404193 小时前
javaSE面试题
java·开发语言·面试
Fiercezm3 小时前
JUC学习
java
无尽的大道3 小时前
Java 泛型详解:参数化类型的强大之处
java·开发语言
ZIM学编程3 小时前
Java基础Day-Sixteen
java·开发语言·windows
我不是星海3 小时前
1.集合体系补充(1)
java·数据结构
P.H. Infinity4 小时前
【RabbitMQ】07-业务幂等处理
java·rabbitmq·java-rabbitmq
爱吃土豆的程序员4 小时前
java XMLStreamConstants.CDATA 无法识别 <![CDATA[]]>
xml·java·cdata