AQS其实是juc包里设计并发实现工具类的抽象类:AbstractQueuedSynchronizer
juc包里有很多类继承了AQS,比如我们经常使用的重入锁:ReentrantLock.NonfairSync 和 ReentrantLock.FairSync,也就是我们经常说的非公平锁和公平锁;CountDownLatch.Sync ; ReentrantReadWriteLock.NonfairSync 和 ReentrantReadWriteLock.FairSync,也就是读写锁
在看源码的时候我们发现,真正继承了AQS的是 ReentrantLock 的内部类 FairSync (或者说是抽象类Sync),但是真正实现了锁的接口的是我们的ReentrantLock类,这里使用引用关系代理了Sync,使用Sync完成了对于接口Lock的实现。
首先我们看下ReentrantLock的成员变量,其实很简单,只有serialVersionUID 和 sync,其中这个sync就是内部继承了AQS的抽象类,ReentrantLock 也是使用sync实现了Lock接口
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final Sync sync;
...
}
我们知道Lock接口定义了一个锁应该提供的功能,首先我们看先Lock对于各个接口的定义(这里我将对jdk中的注释进行翻译,看我英语四级的水平)
public interface Lock {
/**
* 获得一个锁
* 如果锁不可用,则当前线程将出于线程调度目的而禁用,并处于休眠状态,直到获得锁为止(这里看出,对于lock
* 接口,如果阻塞了,就应该让出cpu资源)
*/
void lock();
/**
* 获得一个锁除非被中断了
* 如果锁不可用,则当前线程将出于线程调度目的而禁用,并处于休眠状态,直到获得锁为止,除非遇到下面的两个情况之一
* 1. 获得到了锁
* 2. 被终端了
*/
void lockInterruptibly() throws InterruptedException;
/**
* 尝试获得锁,如果锁在可用状态下将获得锁
* 如果获得了锁,返回true
* 如果没有获得锁,返回fasle
* 经典的用法如下
* Lock lock = ...;
* if (lock.tryLock()) {
* try {
* // 进行串行业务执行
* } finally {
* lock.unlock();
* }
* } else {
* // 执行一些备用行为
* }}
*/
boolean tryLock();
/**
* 在锁可用情况下,在没有超时和中断的情况下获取锁
* 如果锁不可用,则当前线程将出于线程调度目的而禁用,并处于休眠状态,直到获得锁为止,除非遇到下面的三个情况之一
* 1. 在没超时和没中断情况下获得了锁
* 2. 被中断了并响应,跑出异常
* 3. 超时了返回fasle(这里注意,接口定义中是返回false,而不是抛出超时异常)
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* 释放锁
* 一个Lock实现通常会对哪个线程可以释放锁施加限制(通常只有锁的持有者可以释放锁),如果违反了这个限制,可能会抛出一个(未选中的)异常
*/
void unlock();
/**
* 返回绑定在Lock上的Condition (所以说,我们应该维护一个实现类的Condition)
* 只有在当前线程获得了锁的情况下,才可以让condition进行waiting
* 调用了condition#wait 方法,将会在进入waiting之前释放锁,并且在wait返回之前重新获取锁
* 我理解这个东西是用来进行线程通信,
* 比如有个线程释放了锁,那他可以通过 condition#signal 或者 condition#signalAll 唤醒阻塞中的其他线程
*/
Condition newCondition();
}
现在我们来看ReentrantLock是怎么实现的Lock接口的方法,我们发现所有的实现具体都是sync做的,我们不难看出,ReentrantLock 其实就是一个包装类,通过代理的方式,借助sync(AQS)完成了Lock的功能
public class ReentrantLock implements Lock, java.io.Serializable {
public void lock() {
sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
/**
* 这个接口调用的是Sync#nonfairTryAcquire的方法,sync如果是FairSync,为啥也要调用
* nonfairTryAcquire?
* 首先思考下FairSync和NonFairSync的公平和不公平体现在哪里:
* 主要体现在当有新线程试图获得锁的时候如果先竞争锁的话是非公平,如果直接放队列,则是
* 公平锁
* 接着我们来看Lock的经典实用场景:
* Lock lock = ...;
* if (lock.tryLock()) {
* try {
* // 进行串行业务执行
* } finally {
* lock.unlock();
* }
* } else {
* // 执行一些备用行为
* }}
* Lock#tryLock的调用是用户期望得到锁,感觉就是一种特权非公平的用户行为(纯属个人猜测)
*/
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
}
FairSync 和 NonfairSync 都是 ReentrantLock 的内部类,他们是 ReentrantLock 真正实现锁功能的实现类
首先我们来看下 FairSync 的类继承结构:
我们先看下各个抽象类大致负责的内容是什么:
/**
* 独占式线程同步器,提供了创建锁和释放锁的基础操作行为基础,这个类是维护了一种所有权的概念
* 这个类其实很简单,但是却维护了一个很重要的语义,就是锁当前被那个线程锁独有,AQS大量使用了这个语义
*/
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 3737899427754241961L;
/**
* 空的构造函数,说明初始化的时候的语义是改锁没有被任何线程锁占用的场景
*/
protected AbstractOwnableSynchronizer() { }
/**
* 被哪个线程占用着
*/
private transient Thread exclusiveOwnerThread;
/**
* 设置当前被独占的线程,如果是null,表示没有被任何线程占用
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
AbstractOwnableSynchronizer 的两个方法都是final,标识不想被任何类修改,这是个很稳定的类定义
现在我们将进入最复杂的模块:AbstractQueuedSynchronizer
首先我们来看下这个类的成员变量
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 7373984972572414691L;
/**
* 等待队列的头结点,懒初始化,除了初始化,head的改变只能通过 setHead 方法,如果头结点存在,那
* 需要保证头结点的 waitStatus 不是 CANCELLED 状态
*/
private transient volatile Node head;
/**
* 等待队列的尾节点,懒初始化,只有通过 enq 方法新增新的等待节点
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* 同步器的状态,当state <= 0的时候,说明没有被线程持有,如果 state > 0 的时候,说明有线程持有
* 我觉得这个字段得配合 AbstractOwnableSynchronizer 的 exclusiveOwnerThread 一块使用,如果配
* 合不好,可能会出现语义上的差异
*/
private volatile int state;
/**
* 这个值是自旋的时间阙值,用这个时间自旋比park更快(还没太理解)。
*/
static final long spinForTimeoutThreshold = 1000L;
/**
* 特别nb的一个dll类,cas依赖这个类
*/
private static final Unsafe unsafe = Unsafe.getUnsafe();
/**
* state 的内存偏移量
*/
private static final long stateOffset;
/**
* head 的内存偏移量
*/
private static final long headOffset;
/**
* tail 的内存偏移量
*/
private static final long tailOffset;
/**
* Node 节点的 waitStatus 的内存偏移量
*/
private static final long waitStatusOffset;
/**
* Node 节点的 next 的内存偏移量
*/
private static final long nextOffset;
}
成员变量中有很多的内存偏移量: 类内存偏移量,我理解的就是一个类对象在分配了内存空间之后,各个成员的内存地址已经确定,这里只需要设立坐标系,即可得到各个成员变量稳定的坐标地址,知道了一个类对象,知道偏移量,就可以快速找到成员变量对象。
梳理一遍AQS的成员变量,我们发现使用了Node类最为等待队列的节点,不可避免,我们需要分析下Node的数据结构
static final class Node {
/**
* 共享模式的标识
*/
static final Node SHARED = new Node();
/**
* 独占模式标识
*/
static final Node EXCLUSIVE = null;
/**
* 该节点的 waitState 值是 CANCELLED , 表示该线程已经被取消了
*/
static final int CANCELLED = 1;
/**
* 该节点的 waitState 值是 SIGNAL, 表示后继节点线程需要unpark
*/
static final int SIGNAL = -1;
/**
* 该节点的 waitState 值是 CONDITION, 表示线程正在 condition 的等待中
*/
static final int CONDITION = -2;
/**
*
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* 状态字段,只能使用一下值:
* SIGNAL:
* 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).
*/
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.
*/
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;
/**
* 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
*/
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;
}
}