个人学习AQS的一点见解,欢迎各位大佬指点交流
文章目录
概述
先翻译一下注释,了解一下该类设计的目的,英文捉急,各位凑活看看
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 {@code int} value to represent state.
这个类旨在为大多数依赖于原子值来表示状态的同步器提供基础
许多并发类的框架例如ReentrantLock都是基于它实现的
流程图
内部结构
类属性
首先有一个状态来表示当前资源的情况,不同子类实现,该状态代表的含义各不相同,本文都是拿ReentrantLock举例来说明
初始化0,代表资源没有被占用,state>0则表示被占用,由于ReentrantLock是可重入锁,资源可以被同一个线程多次获得,所以state值可以>1
/**
* The synchronization state.
*/
private volatile int state;
private transient volatile Node head;//队头节点
private transient volatile Node tail;//队尾节点
private transient Thread exclusiveOwnerThread;//当前独占资源的线程
节点
什么是节点呢,是AQS中定义的一个内部类
来一下作者的注释
The wait queue is a variant of a “CLH” (Craig, Landin, and
* Hagersten) lock queue. CLH locks are normally used for
* spinlocks.
该队列是CLH 锁队列的一个变种,CLH锁通常用于自旋锁
A “status” field in each node keeps track of whether a thread should block
每一个节点中的status字段的意义是确定每个线程是否应该被阻塞
A node is signalled when its predecessor releases.
在前置节点释放锁后 当前节点会被唤醒
Each node of the queue otherwise serves as a
* specific-notification-style monitor holding a single waiting
* thread.
队列中的每一个节点充当一个握有等待线程的特殊通知的”观察者”
A thread may try to acquire if it is
* first in the queue. But being first does not guarantee success;
* it only gives the right to contend. So the currently released
* contender thread may need to rewait
如果一个线程是队列的头节点,它会尝试去获得锁,但这并不能保证一定成功,只是给予它一个机会去争夺锁,因此可能会重新进入队列等待
Next 节点主要是被头节点用于唤醒下一个节点(头节点不存线程,故下一个为队列的第一个节点),设置时要避免
volatile int waitStatus;//代表节点的状态
volatile Node prev;//前驱节点
volatile Node next;//后继节点
volatile Thread thread;//包含的线程
acquire方法
AQS获取锁的方法,为模板方法
主要目的概括为三点
1 tryAcquire 先去尝试获得锁 (该方法为钩子方法,默认抛出异常,由子类去提供具体实现)
2 获取失败 addWaiter 将当前线程 加入等待队列
3 入队的线程自旋获得锁,视情况把自己挂起,等待前方结点唤醒
ReentrantLock中使用lock方法会调用到
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire
看一下ReentrantLock的实现,ReentrantLock中的tryAcquire方法会调用到图片中的方法
AddWaiter
获取锁失败的线程会调用到该方法,用于加入等待队列,并返回新创建的节点
Enq
队列未创建,或CAS竞争设置尾节点失败进入enq方法,自旋的进入队列
下图说明这种尴尬时期
acquireQueued
阴间翻译。。。
Acquires in exclusive uninterruptible mode for thread already in queue.
在队列中的节点以不中断的独占模式获取锁(直到获得锁成功)
SIGNAL: The successor of this node is (or will soon be) blocked (via park), so the current node must unpark its successor when it releases or cancels.
该状态表明,当前节点的后继节点是阻塞(挂起)的,所以当前节点在释放或者取消时必须唤醒后继节点
CANCELLED: This node is cancelled due to timeout or interrupt. Nodes never leave this state. In particular,a thread with cancelled node never again blocks.
当前节点由于超时或者被中断而被取消,已取消的节点不会再被阻塞
shouldParkAfterFailedAcquire
该方法决定当前线程是否可以被挂起,如果前驱节点状态为signal则直接可以放心挂起,不是就要向前遍历,把已取消的节点筛除掉,再把新的前驱节点的值改为signal,返回false,因为外面是死循环是想再tryAcquire尝试获取一下锁,再失败了又会回到该方法,此时由于上次设置的前驱节点为signal则可以安心挂起
parkAndCheckInterrupt
该方法时阻塞线程,记录中断状态,然后清除
为什么要返回中断状态?
因为park方法与wait()方法都是挂起线程的方法,不同的是,如果线程在中断状态,调用wait会抛出异常,而park方法则直接返回。
为了避免在线程在acquireQueue方法中一直进行无效的循环,所以一定要未能获取到锁的线程挂起,如果当前线程的前置节点不是头节点并且当前线程的状态是中断状态,则第一次循环的时候,线程没有被挂起,但是执行了Thread.interrupted()方法,线程的中断状态消失了,在第二次循环的时候就会被成功挂起了!
在acquireQueue方法记录线程原本的中断状态,获取锁之后还原中断状态
Release 方法
也是AQS的模板方法
ReentrantLock中使用unlock方法会调用到
TryRelease
还是来看reentrantlock的实现方法
unparkSuccessor
为什么从后往前遍历?
如上文enq解释的情况,避免遍历时断裂,因为prev在cas之前所以是可靠的,而next在cas之后是不可靠的