在 Lock 中,用到了一个同步队列 AQS,全称AbstractQueuedSynchronizer,它是一个同步工具也是 Lock 用来实现线程同步的核心组件。如果你搞懂了 AQS,那么 J.U.C 中绝大部分的工具都能轻松掌握。
从使用层面来说,AQS 的功能分为两种:独占和共享
独占锁,每次只能有一个线程持有锁,比如 J.U.C包下的的 ReentrantLock 就是以独占方式实现的互斥锁
共 享 锁 , 允 许 多 个 线 程 同 时 获 取 锁 , 并 发 访 问 共 享 资 源 , 比 如 J.U.C包下的ReentrantReadWriteLock
我们下看一下设计者对AbstractQueuedSynchronizer的整体介绍
/**
* 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. Subclasses
* must define the protected methods that change this state, and which
* define what that state means in terms of this object being acquired
* or released.
* */
我们通过这段话可以分析出设计考虑要素
1.锁的实现依赖一个先进先出的等待队列
2.依赖一个原子的属性值代表状态,通过一个受保护的方法来更改状态,从而代表锁的获取或者释放
我们先分析一下AbstractQueuedSynchronizer类的结构
// node是一个静态内部类,我们知道AQS是一个队列,队列里面每个节点就是一个Node
static final class Node {
...
}
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
// 代表AQS等待队列的头,只能通过setHead方法来设置修改,而且,如果head节点存在,则这个等待状态一定不是CANCELLED,至于CANCELLED我们后面文章中分析
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
// 代表AQS等待队列的末尾,只能通过enq方法来往最后添加一个节点
private transient volatile Node tail;
/**
* The synchronization state.
*/
// 用这个字段代表锁的状态
private volatile int state;
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
// 对状态的修改,final修饰的,有点类似于被volatile修饰的变量的写操作
protected final int getState() {
return state;
}
/**
* Sets the value of synchronization state.
* This operation has memory semantics of a {@code volatile} write.
* @param newState the new state value
*/
// 对状态的修改,final修饰的,有点类似于被volatile修饰的变量的写操作
protected final void setState(int newState) {
state = newState;
}
/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a {@code volatile} read
* and write.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
//提供一个CAS实现的原子方法来对状态进行修改
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
我们可以看到AQS里面的结构:
1.维护了一个colatile修饰的state变量来代表锁的状态
2.维护了一个元素是node节点的队列
3.存在head(头结点)和tail(尾节点)
对AbstractQueuedSynchronizer类的属性了解后,我们着重看一下这个静态内部类Node的结构,其代码如下
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
// 是一种共享锁的标识,比如我们说的读锁,或者像CountDownLatch的实现就是用的SHARE
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
// 是一种独占锁的标识,比如我们说的ReentrantLock
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
// 表示等待超时后的状态
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
// 线程的等待状态 表示后继线程需要被唤醒
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
// 代表受condition条件控制的时候,比如调用了await方法
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
// 共享锁的一种传递机制,我们唤醒一个锁,会继续唤醒下一个锁,这样一直唤醒所有的共享锁
static final int PROPAGATE = -3;
/**
* Status field, taking on only the values:
* 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. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* 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.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
/**
* SIGNAL: 当前节点的后继节点处于等待状态时,如果当前节点的同步状态被释放或者取消,
* 必须唤起它的后继节点
*
* CANCELLED: 一个节点由于超时或者中断需要在CLH队列中取消等待状态,被取消的节点不会再次等待
*
* CONDITION: 当前节点在等待队列中,只有当节点的状态设为0的时候该节点才会被转移到同步队列
*
* PROPAGATE: 下一次的共享模式同步状态的获取将会无条件的传播
* waitStatus的初始值时0,使用CAS来修改节点的状态
*/
volatile int waitStatus;
/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
// 指向上一个节点的指针
volatile Node prev;
/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
// 指向下一个节点的指针
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
// 每个node其实封装的是一个线程
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
// 链接到下一个节点的等待条件,或特殊的值SHARED
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
// 一种判断是不是共享锁的方法
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
// 获取前一个node节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
通过node的数据结构我们可以看出来:
1.每个node是对一个Thread线程的封装
2.node存在两个指针,prev和next分别指向上一个节点和下一个节点
3.node中每个节点也会记录状态
其实分析到这里,我们可以很清楚的知道AQS结构了,其实它并没有多神秘,就是一个双向队列
初始化状态,队列为空的情况下,head和tail都是null
首次添加元素为例,调用enq(node)方法
第一次循环
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
// 将node元素放到队列里面去
private Node enq(final Node node) {
// 自旋
for (;;) {
// 把tai节点赋给t
Node t = tail;
// 如果t==null,表示此时tail为null,表示此时队列中没有元素,需要初始化队列
if (t == null) { // Must initialize
// new Node()创建一个空的node节点
// 通过CAS将这个node节点赋给head
if (compareAndSetHead(new Node()))
// tail赋值,此时的队列状态应该为图1队列初始化状态
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
第一次循环后的队列应该是下图这样的,但是注意,队列中并没有这个node节点,其实这一次的循环只是为了给head和tail赋值,让他们不等于null
第二次循环
// 将node元素放到队列里面去
private Node enq(final Node node) {
// 自旋
for (;;) {
// 把tai节点赋给t,此时tail和head都指向一个空的node节点
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 第二次循环,会进入到这个地方,将node.prev指向t
node.prev = t;
// 通过CAS设置tail节点为node
if (compareAndSetTail(t, node)) { // 2
// t.next指向node,node是我们要添加进去的节点
t.next = node; // 3
// 返回t节点,结束
return t;
}
}
}
}
第二次循环后的结果如下图
所以在多次调用enq()方法之后,AQS队列应该是如下图展示
调用release方法其实就是解锁的过程,此方法应该在具体的锁实现中通过unlock方法调用。其实下面的方法需要结合具体的锁去分析,我们只是简单看一下源码
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
// 我们看参数arg,为什么要传入一个int类型的参数呢?因为锁是允许重入的,lock多少次,就要unlock多少次,通过次数来记录释放几次锁
public final boolean release(int arg) {
// tryRelease()这是个模板方法,再具体的锁实现里面实现了此方法的逻辑;其实这个方法就是把锁状态次数减去arg次,然后判断锁状态是不是0
if (tryRelease(arg)) {
// 把head节点赋值给h
Node h = head;
// 判断head节点不是null,并且waitStatus!=0,就去调用unparkSuccessor方法唤醒后续节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
// 判断当前node的状态,ws<0,表示是处于signal状态,是可以被唤醒的
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//得到 head 节点的下一个节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
//如果下一个节点为 null 或者 status>0 表示 cancelled 状态.
//通过从尾部节点开始扫描,找到距离 head 最近的一个waitStatus<=0 的节点
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 通过LockSupport方法唤醒指定线程
LockSupport.unpark(s.thread);
}
为什么在释放锁的时候是从 tail 进行扫描?
我觉得有必要单独拿出来说一下,我们再回到 enq那个方法、。在标注部分的代码来看一个新的节点是如何加入到链表中的
private Node enq(final Node node) {
// 自旋
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 1 将新的节点的 prev 指向 tail
node.prev = t;
// 2 通过 cas 将 tail 设置为新的节点,因为 cas 是原子操作所以能够保证线程安全性
if (compareAndSetTail(t, node)) {
// 3 设置原 tail 的 next 节点指向新的节点
t.next = node;
return t;
}
}
}
}
在 cas 操作之后,t.next=node 操作之前。 存在其他线程调用 unlock 方法从 head开始往后遍历,由于 t.next=node 还没执行意味着链表的关系还没有建立完整。就会导致遍历到 t 节点的时候被中断。所以从后往前遍历,一定不会存在这个问题
那通过这个队列是怎么实现获得锁排队和释放锁的呢,我们从具体的锁分析,下一节ReentrantLock具体分析
本文是综合自己的认识和参考各类资料(书本及网上资料)编写,若有侵权请联系作者,所有内容仅代表个人认知观点,如有错误,欢迎校正; 邮箱:[email protected] 博客地址:https://blog.csdn.net/qq_35576976/