AQS 数据结构
数据结构分为两部分。第一行的同步队列,用于排队所有的的等待线程一次获取锁。第二、三行是条件队列,每一行对应当前lock锁的一个条件Condition。
还有一部分没有画出来,是条件队列中节点在signal()方法中会enq入队到第一行的同步队列中去获取锁,这时条件队列的node节点中的prev、next会指向同步队列的节点上。
同步队列(sync queue)
基于双向链表的同步队列,头尾指针head、tail。同步队列的修改是线程安全的,因为会有多线程的竞争。
每一个node在同步队列中都代表一个正在等待获取锁的线程,也是首次获取锁失败后才进入同步队列进行等待的线程。第一个node代表当前获得锁的线程,由head指向首个节点。
重要属性解析:
- prev指针作用:prev == head 成功,是去争夺锁的前提条件。只有当一个node的prev已经是head时,该node才有可能获得锁,即就是锁的获取过程遵从sync queue的顺序排列过程。
- next指针作用:当prev在释放锁的时候,通过next指针找到下一个需要获得锁的节点及其对于线程。
- waitStatus:这个属性比较重要,0是默认值,和PROPATION一样是指要向后传递,但PROPATION只设置在首节点(即head);CONDITION指该node为一个等待信号的节点,常用在条件队列中;CANCELED是指当前node被取消了,可能出现在同步队列和等待队列中(被取消的原因可能由释放锁失败,此作用在await()#fullRelease()方法中,后续会讨论)。
node有五个最重要的属性:
- prev:前一个节点指针
- next:后一个节点指针
- waitStatus:当前节点的等待状态,有0(默认)、PROPAGATION、SIGNAL、CONDITION、CANCELED 五种
- thread:当前排队的线程
- nextWaiter:mode,有三种值Node.SHARED,Node.EXCLUSIVE,条件队列中下一个node的指针
node两种节点类型:
- EXECLUDE:排它锁
- SHARED:共享锁
条件队列(condition queue)
基于单向链表的非同步队列,头尾指针为firstWaiter、lastWaiter。条件队列为非同步队列,修改条件队列的线程必须获得当前锁lock,故不存在多线程竞争。
node有五个重要属性:
- nextWaiter:条件队列中下一个节点指针
- thread:当前排队的线程
- prev:保留,后面指向同步队列的某节点上
- next:保留,后面指向同步队列的某节点上
- waitStatus:条件队列只有 CONDITION 一种值
节点类型:
- 此时waitStatus就是指针,没有特殊类型
条件队列节点enq到同步队列
条件队列的原理就是,先调用await() 让当前线程进入到某个条件的条件队列中睡眠等待,直到signal信号唤醒后,争夺锁成功返回继续执行await()后面的代码,以此达到最终一种 等待-唤醒
模式实现线程间通信协作。
而条件队列节点被唤醒后若想重新获取到锁,一定要先node进入到sync queue中排队获取排它锁,将来才有可能等到锁。因为此时可能会有线程已经获得锁,同时有很多线程已经在等待获取锁了,故只要一样加入等待锁的同步队列即可。
AQS都有哪些特点:
这里会先汇总AQS的一些特点以及都包含了哪些类型的锁,有一个整体的认知。
- 共享锁、排它锁
- 公平锁、非公平锁
- 阻塞式锁、非阻塞式锁、超时锁
- 可中断锁、不可中断锁
- 可重入锁
- Condition条件
- 使用了LockSupport类的静态方法实现精准的线程睡眠等待、线程唤醒
AQS在jdk源码中有哪些实现:
- 共享锁:Semaphore、CountdownLatch
- 排它锁:ReentrantLock
- 可重入锁:ReentrantLock
- 共享、排它锁:ReentrantReadWriteLock
- ThreadPoolExecutor#Worker 工作任务,继承自AQS
AQS源码:
排它锁源码:
排它锁AQS提供多种形式:
1. 阻塞式不可中断获取锁:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)返回线程是否被标记中断:true标记中断,调用执行当前线程中断;false未标记,锁获取成功返回。
CAS插入新节点到sync队列,直到成功返回node:
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;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
对入队node进行二次争夺锁:失败则park();成功则返回锁。
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)) { //第二次tryAcquire()
setHead(node); //成功后head指向当前node
p.next = null; // help GC //前驱node,next指针置为空
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && //判断是否应该park()
parkAndCheckInterrupt()) //park() &