AQS -- AbstractQueuedSychronizer
简介
AQS是什么:
AQS是JDK中JUC中构建锁和其他同步装置的基本框架。其底层实现的数据结构是一个双向链表。
重点成员变量
/**
* 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.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
state
判断共享资源是否被占用的标志位(就是锁是否被占用)。
使用volatile修饰的int类型,保证了当前信息线程之间的可见性,
因为锁有两种状体
1. 独占: 一个线程占用的时候,其他线程无法占用
2. 共享: 一个线程占用的时候,其他线程也可以占用
所以state不用布尔类型,而是用int类型,可以提供有线程占用的数量
head 和 tail
head 和 tail 顾名思义是头节点和尾节点。
作用是线程在获取锁失败的情况下,可以进去缓存队列去等待获取锁。
Node对象
static final class Node {
/** waitStatus 的四个等待状态 */
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
/** 等待状态 */
volatile int waitStatus;
/** 上一个Node对象 */
volatile Node prev;
/** 下一个Node对象 */
volatile Node next;
/** 线程对象 */
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.
*/
Node nextWaiter;
重点方法
acquire
尝试获取锁立即返回
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这里的acquired方法用的是public 和 final来修饰这个方法,说明就是让所有继承的子类直接来调用这个方法,且不允许重写。
方法内容:
- 尝试去获取锁且获取锁失败
- 进行排队等待锁(addWaiter操作是将当前线程放入到等待队列中,有点类似与LinkedHashMap的放入尾节点操作)
tryAcquire
获取锁,如果没有获取到,进入等待队列直到获取到锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
当前方法就抛出了一个异常,就是让继承的子类去重写这个方法,否则就抛出不支持该操作的异常
acquireQueued
这个方法是当前线程会根据公平性原则来进行阻塞等待,直到获取锁为止(即排队等待锁);并且返回当前线程在等待过程中有没有中断过
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//判断是否需要挂起当前节点,如果需要则挂起节点。这一步是为了让头节点和次节点去自旋获取锁,而其他节点则是挂起或者正在被挂起的状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//这一块只有在try代码块内抛出异常的时候,才会执行。目的是将node置为Cancel状态,并执行一些清理操作
if (failed)
cancelAcquire(node);
}
}
release 释放锁操作
因为部分节点在acquire方法的时候,无法获取锁且被挂起,而release方法在释放锁的时候,会唤醒挂起的线程去尝试获取锁。
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//如果当前node内的等待状态不是Cancel则去重置waitStatus为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
//如果node的下一个node为null或者等待状态是Cancel则去为节点开始遍历获取node
//为什么是从后往前便利获取是因为:因为插入的时候,cas和next节点赋值的时候可能会有其他线程打断,导致从前往后遍历会出现null。
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//唤醒线程
if (s != null)
LockSupport.unpark(s.thread);
}
和acquire方法差不多,tryRelease方法明示让子类继承的时候去重写,否则就抛出异常。
release 方法则是释放锁的时候去唤醒挂起的线程,而线程被唤醒之后会在acquireQueued中继续执行获取锁的操作
问题
- 为什么unparkSuccessor唤醒线程的时候,当node的下一节点为null或者等待状态为Cancel的时候,是从尾结点开始往前遍历获取node对象
答:因为插入操作的CAS操作和next节点赋值的时候可能会有其他系欸DNA打断,导致从前往后遍历的时候出现null。
就是addWaiter方法中的
if (compareAndSetTail(pred, node)) {
//这一步操作
pred.next = node;
return node;
}