AbstractQueuedSynchronizer(抽象的同步等待队列)的简称。
之前讲到的ReentrantLock、Semaphore、CountDownLatch都是基于AQS来实现的。可以理解为AQS是一个抽象模板,帮我们定义了一些列的规范和工具,只需要重写其抽象方法就可以自己实现一把锁。
(一)、AQS模板内部结构
- 共享变量标记,资源可用状态(state)
- 锁获取线程等待队列(Queue(Node))
- 阻塞条件变量(Condition)
- 阻塞条件唤醒队列(Queue(Node))
1.1 AQS的执行流程
以ReentrantLock(独占锁)对象举例
ReentrantLock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
lock.lock();
try {
//...
}finally {
lock.unlock();
}
- 多个线程进入到lock方法,依据CAS原子性来尝试修改获取资源标志(state),如果修改成功设置为true,如果修改失败则进入等待队列。
- 获取锁的线程如果调用Condition的await方法进入条件等待队列,同时将资源释放。
- 如果条件被唤醒则重新执行第一步。
1.2 AQS模板内部属性说明
1.2.1 state
表示资源的可用性,多个线程通过CAS操作尝试获取资源。
1.2.2 head/tail
- head(Node):队列的第一个元素
- tail(Node):队列的尾部元素
1.2.2 Node对象属性说明
线程资源访问方式
- SHARED : 共享,多个线程可以同时执行,如Semaphore/CountDownLatch
- EXCLUSIVE: 独占,只有一个线程能执行,如ReentrantLock
当前线程的状态
- waitStatus:值为0,初始化状态,表示当前节点在队列中,等待着获取锁。
- CANCELLED:值为1,表示当前的线程被取消;
- SIGNAL:值为-1,表示当前节点的后继节点包含的线程处于阻塞状态,需要运行,也就是unpark;
- CONDITION:值为-2,表示当前节点在等待condition,也就是在condition队列中;
- PROPAGATE:值为-3,表示当前场景下后续的acquireShared能够得以执行;
1.2.3 主要API
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
1.2.4 同步等待队列
- 基于双向链表(Node特性)的数据结构队列
- 线程获取共享资源失败时,则会构建Node对象节点加入到该队列中。
- 条件等到队列中被唤醒,或加入到该队列中。
1.2.5 条件等待队列
AQS中条件队列是使用单向列表保存的,用nextWaiter来连接:
- 调用await方法阻塞线程。
为什么要有条件等待队列?
满足某个条件后被唤醒才能进入到‘同步等待队列’进行资源的获取
1.3 如何自己写一个lock
确认下面几个问题:
- 如果线程没有获取到资源,先挂起(park),激活(unpark)后是怎么样重新抢夺资源的?
- 如果锁重入了,那么标记(state)肯定是大于1的,在锁释放的时候是如何减到1的?
-
- 可重入锁的调用者要么是递归要么是一个线程调用其他加锁的方法,每一次的递归结束后都会调用释放锁,所以最终是会减到1。
- 等待队列和条件等待队列为什么不是
queue
而是双向链表? - 如果线程放到了链表中,那么为什么还要将线程挂起再激活?
-
- 线程挂起和放到链表中是两个完全不同的操作,就比如为了节省手机电量先将手机关机,到了合适的时候再打开继续使用是一个道理。
- 如何设计公平锁和非公平锁、如何设计可重入锁?
-
- 查看第二部分《源码分析》
(二)、源码分析
2.1 公平非公平实现
2.1.1 非公平锁实现
final void lock() {
//进来以后先尝试加锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
2.1.2 公平锁实现
final void lock() {
//进来以后直接入队
acquire(1);
}
2.2 可重入锁实现
protected final boolean tryAcquire(int acquires) {
//设置当前的线程为获取锁的线程
setExclusiveOwnerThread(current)
//如果获取锁的线程为当前线程则获取锁成功
if (current == getExclusiveOwnerThread()) {
return true;
}
return false;
}
2.3 入队和出队实现
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
2.3.1 入队实现
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; //队尾
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
//如果队尾为空则没有初始化,需要初始化
//已经初始化的队列,将该节点的入队引用设置成队尾,并将队尾的Node的出队引用设置成该Node
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
// 队列初始化,首次队列只有一个节点,所有队头和队尾都设置成该node
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 队列中已经存在数据
// 将该节点的入队引用设置成队尾,并将队尾的Node的出队引用设置成该Node
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
2.3.2 出队实现
在队列中尝试获取资源,一直到获取资源后才返回,返回值为true:标识线程被中断。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取前驱结点,这里的head标识队首的意思,如果队首为当前节点的前驱结点
//则表示当前节点是队列中的第二个,可以尝试获取资源
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//shouldParkAfterFailedAcquire:根据waitStatus判断是否可以阻塞
//parkAndCheckInterrupt:阻塞当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);//取消获取的锁资源
}
}