一、AQS
AQS,即AbstractQueuedSynchronizer抽象同步队列,是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的,因此这个类非常重要。
因此在谈到并发包的源码时,先讲一下AQS
简单的先做下AQS的加锁流程
二、源码分析
2.1 继承与实现
可以看到AQS继承了AbstractOwnableSynchronizer。由线程独占的同步器,这个类为创建锁和相关同步器提供了基础,这个类比较简单。
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/** Use serial ID even though all fields transient. */
private static final long serialVersionUID = 3737899427754241961L;
/**
* Empty constructor for use by subclasses.
*/
protected AbstractOwnableSynchronizer() { }
/**
* 互斥模式下的当前线程
*/
private transient Thread exclusiveOwnerThread;
/**
* 设置当前独占访问的线程,是拥有锁的线程
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
/**
* 返回设置的线程
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
2.2 属性
AQS是一个FIFO的双向队列,内部通过节点head和tail纪录队首和队尾元素,队列元素的类型为Node。
先看一下Node节点
static final class Node {
/** 标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的 */
static final Node SHARED = new Node();
/** 标记线程是获取共享资源时被阻塞挂起来放入AQS队列的 */
static final Node EXCLUSIVE = null;
/** 表示当前线程被取消了 */
static final int CANCELLED = 1;
/**后续节点处于等待状态,而当前节点的线程如果释放了同步状态或被取消,将会通知后继节点,使后继节点的线程得以运行 */
static final int SIGNAL = -1;
/** 线程在条件队列里面等待 */
static final int CONDITION = -2;
/**
* 释放共享资源时需要通知其他节点
*/
static final int PROPAGATE = -3;
/**
* 纪录当前线程的等待状态
*/
volatile int waitStatus;
/**
* 当前节点的前驱节点
*/
volatile Node prev;
/**
* 当前节点的后继节点
*/
volatile Node next;
/**
* 当前节点的线程
*/
volatile Thread thread;
/**
* 条件线程的后继节点
*/
Node nextWaiter;
/**
* 判断节点状态是否是共享模式
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回当前节点的前驱节点
*/
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;
}
}
AQS的主要属性:
//AQS队列的头结点
private transient volatile Node head;
/**
* AQS队列的尾节点
*/
private transient volatile Node tail;
/**
* 同步状态
*/
private volatile long state;
/**
* 返回当前的同步状态
*/
protected final long getState() {
return state;
}
/**
* 设置当前的同步状态
*/
protected final void setState(long newState) {
state = newState;
}
/**
*CAS方式设置同步状态
*/
protected final boolean compareAndSetState(long expect, long update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapLong(this, stateOffset, expect, update);
}
可以看到在AQS中维持了一个唯一的状态信息state,可以通过方法来修改其值。对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的可重入次数;对于Semaphore来说,state用来表示当前可用信号的个数;对于CountDownlatch来说,state用来表示计数器当前的值
对AQS来说,线程同步的关键是对状态值state进行操作。根据state是否属于一个线程,操作state的方式分为独占方式和共享方式。
独占方式:只有一个线程能执行,如ReentrantLock
共享方法:多个线程可以同时执行,如Semaphore和CountDownlatch
2.3 获取与释放资源–独占方式
public final void acquire(long arg) {
//很短但是有很多方法,详细看下面的
//tryAcquire成功,不调用此方法,失败则调用acquireQueued方法加入队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//独占模式下,尝试去获取锁(子类需要重写此方法)
protected boolean tryAcquire(long arg) {
throw new UnsupportedOperationException();
}
//获取失败,将当前线程封装为Node.EXCLUSIVE的node节点并添加至AQS阻塞队列尾部
private Node addWaiter(Node mode) {
//封装当前线程及锁的模式
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//取得阻塞队列的尾部节点
Node pred = tail;
//如果尾节点不为null
if (pred != null) {
//新节点的前驱指向尾节点
node.prev = pred;
//设置当前节点为尾节点,CAS,确保入队是原子操作
if (compareAndSetTail(pred, node)) {
//将旧的尾节点的后续指向新节点
pred.next = node;
//返回新加入的node
return node;
}
}
//阻塞队列还未初始化,节点入队
enq(node);
return node;
}
//节点入队操作
private Node enq(final Node node) {
//进入死循环
for (;;) {
//获取阻塞队列的尾节点
Node t = tail;
//尾节点为null,还未初始化,进入if语句
if (t == null) { // Must initialize
//初始化阻塞队列,将新的节点设置为头节点
if (compareAndSetHead(new Node()))
//尾节点指向头节点(队列中此时只有一个元素)
tail = head;
//初始化后进行第二次for循环,进入else语句
} else {
//尾节点设置为传入新节点的前驱
node.prev = t;
if (compareAndSetTail(t, node)) {
//将旧的尾节点后继设置为传入的节点
t.next = node;
//退出for循环,唯一出口
return t;
}
}
}
}
//当前线程在死循环中尝试获取同步状态
final boolean acquireQueued(final Node node, long arg) {
//失败标志
boolean failed = true;
try {
//打断标志
boolean interrupted = false;
for (;;) {
//获得当前节点的前驱节点的引用p
final Node p = node.predecessor();
//如果p是阻塞队列的头部,就尝试获取锁
if (p == head && tryAcquire(arg)) {
//获取到,将node设置为head节点
setHead(node);
//将原头部的next设置为null,help GC
p.next = null; // help GC
//加锁成功设置标记为false
failed = false;
//返回打断标志
return interrupted;
}
//如果满足条件就挂起当前线程&&获取是否中断
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//如果失败
if (failed)
cancelAcquire(node);
}
}
//判断是否可以将节点挂起
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前驱节点的等待状态
int ws = pred.waitStatus;
//如果是SGINAL,代表前驱节点完成后会唤醒后继节点,当前节点就可以挂起了
if (ws == Node.SIGNAL)
return true;
//大于0是CANCEL状态
if (ws > 0) {
/*
* 清除已经被取消的前驱节点
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* CAS操作设置前驱节点状态为SIGNAL,后面会返回false,然后重复判断
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//挂起时检查中断标记
private final boolean parkAndCheckInterrupt() {
//挂起当前线程
LockSupport.park(this);
//返回中断标记,如果是正常唤醒则返回false,中断醒来则返回true
return Thread.interrupted();
}
//进入这里代表抛出异常,进行异常处理
private void cancelAcquire(Node node) {
//节点不存在则忽略
if (node == null)
return;
//节点线程设置为null
node.thread = null;
// 跳过已取消的节点,同上面CANCENL状态操作一样
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// 把当前节点的waitStatus状态设置为取消
node.waitStatus = Node.CANCELLED;
//如果是尾节点,直接CAS尾节点
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
//如果当前节点的前驱节点不是头节点且后面的节点等待他唤醒
//将前驱节点与后继节点相连
compareAndSetNext(pred, predNext, next);
} else {
//当前节点的前置是头结点,直接唤醒后继节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
//醒来后判断是否被打断
//是则将自己中断
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
2.4 线程释放锁
//释放锁的操作
public final boolean release(long arg) {
//tryRelease需要子类自己实现
if (tryRelease(arg)) {
Node h = head;
//判断有无需要被唤醒的节点
if (h != null && h.waitStatus != 0)
//唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
/*
* 判断表示是否为小于0,把标记设为0
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* 如果后继节点为null或被取消
*/
Node s = node.next;
if (s == null || s.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.unpark(s.thread);
}
基本上就这些了,比较复杂,要慢慢来
三、总结
AQS在获取锁的同时,还会维护一个FIFO队列。获取锁失败的线程会加入队列,并在队列中进行自旋。自旋的同时会判断前驱节点的状态,然后使用LockSupport.park方法进行阻塞。当前驱节点是头节点且成功获取了同步状态时,线程移除队列。释放同步状态的同时,根据节点状态判断后继节点是否需要唤醒,然后唤醒头节点的后继节点
自己做的流程图,也不知道对不对,有错及时分享下