提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 一、怎么解释AQS是什么?
- ==AQS的本质是JUC包下一个抽象类,AbstractQueuedSynchronizer (抽象的队列式同步器)==
- 二、AQS核心底层和Lock是什么关系?
- ReentrantLock的互斥锁功能就是基于AQS实现的。
- 优先聊一下lock方法的区别。
- 分析一下acquire方法中做了什么事
- 三、AQS如何尝试获取资源?
- 非公平锁的tryAcquire实现
- 公平锁的实现
- 四、AQS获取资源失败如何排队?
- 五、AQS排队后如何重新尝试获取资源?
- 六、AQS如何释放资源?
一、怎么解释AQS是什么?
AQS的本质是JUC包下一个抽象类,AbstractQueuedSynchronizer (抽象的队列式同步器)
- AQS是JUC下的一个基础类,目的是为了提供一个共性的功能,让其他类去继承,比如ReentrantLock,CountDownLatch,Semaphore,ThreadPoolExecutor…………
二、AQS核心底层和Lock是什么关系?
ReentrantLock的互斥锁功能就是基于AQS实现的。
通过源码可以看到,ReentrantLock类并没有直接继承AQS,而是ReentrantLock的内部类Sync继承了AQS。
Sync也是一个抽象类,他下面有两个实现。一个FairSync,一个NonfairSync。一个公平锁,一个非公平锁。
在ReentrantLock中,公平锁和非公平锁对于lock方法和tryAcquire方法的实现是不同的。
优先聊一下lock方法的区别。
// 非公平锁的lock方法
final void lock() {
// 不管是否有线程在持有锁资源,直接尝试将state从0改为1,尝试拿锁。
if (compareAndSetState(0, 1))
// 拿锁成功了。将当前线程设置到exclusiveOwnerThread
setExclusiveOwnerThread(Thread.currentThread());
else
// 前面抢锁失败走acquire
acquire(1);
}
// 公平锁的lock方法
final void lock() {
acquire(1);
}
分析一下acquire方法中做了什么事
// acquire方法实现
public final void acquire(int arg) {
//1、tryAcquire方法:尝试获取锁资源的过程。拿到锁返回true,反之返回false。
// 没拿到锁,才会走2和3。
//2、addWaiter方法: 将当前没拿到锁的线程封装为Node,添加到同步队列。
//3、acquireQueued方法: 长时间等待需要挂起线程,并且等到线程排到第一名时,
// 需要再次尝试获取锁资源
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
三、AQS如何尝试获取资源?
tryAcquire方法是如何尝试获取锁资源的,tryAcquire方法有两种实现,一种是公平,一种是非公平。
- 判断state为0,那就根据情况抢锁
- state不为0,但是当前线程持有锁,那就走锁重入的逻辑
- 前面都不满足,告辞!
非公平锁的tryAcquire实现
// 非公平锁的实现。
final boolean nonfairTryAcquire(int acquires) {
// 拿到当前线程。
final Thread current = Thread.currentThread();
// 获取state
int c = getState();
// 判断state是否为0
if (c == 0) {
// 当前没有线程持有锁。非公平锁直接尝试抢锁,抢成功就返回true
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 有线程持有锁。那就判断持有锁的线程是不是当前线程。
else if (current == getExclusiveOwnerThread()) {
// 到这说明是锁重入操作。
// 将state + 1
int nextc = c + acquires;
// 判断+1之后,如果小于0,说明超过int正整数的取值范围了,无法再次重入~
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 将 + 1后的值赋值给state
setState(nextc);
// 返回true,锁重入成功~
return true;
}
//没拿到锁,返回false
return false;
}
公平锁的实现
// 公平锁的实现。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平锁这里多了一行hasQueuedPredecessors()
// 如果没有线程持有锁资源,优先查看是否有排队的线程
// 1、如果没有线程排队,返回false,代表可以抢锁。
// 2、如果有排队的,但是当前线程排在“第一名”,返回false,代表可以抢锁。
// 3、如果有排队的,但是当前线程没有排在“第一名”,返回true,代表不可以抢锁。
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;a
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
四、AQS获取资源失败如何排队?
如果获取锁资源失败,会执行addWaiter方法,去做排队操作。
- 将当前线程封装Node。
- 如果同步队列为null,需要优先初始化一个虚拟的Node节点
- 将当前Node添加到tail的后面。
private Node addWaiter(Node mode) {
// 将当前线程封装Node
Node node = new Node(Thread.currentThread(), mode);
// 拿到尾结点
Node pred = tail;
// 不为null,代表现在有Node对象。
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果pred是null,
enq(node);
return node;
}
// 将node添加到同步队列
private Node enq(final Node node) {
// 死循环是为了确保一定能添加成功
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
// 初始化一个没有线程信息的Node,作为头尾
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
五、AQS排队后如何重新尝试获取资源?
- 挂起需要确保prev节点的状态为-1。
- 重新获取锁资源需要确保当前node是head.next,并且基于tryAcquire去获取锁资源。
// 排队后的挂起操作和获取锁资源的操作
// node就是刚刚去排队的Node
final boolean acquireQueued(final Node node, int arg) {
// 拿锁失败了么??true
boolean failed = true;
try {
// 死循环,拿到锁才能走!!
for (;;) {
// 拿到当前Node的上一个Node
final Node p = node.prev;
// 只有head.next的node才有资格抢锁
if (p == head && tryAcquire(arg)) {
// 说明拿到锁资源了。
// 当前node成为新的head,线程和prev都设置为null
setHead(node);
p.next = null;
failed = false;
// 拿到成功,返回中断标记位(这里省略了这部分代码)
return false;
}
// 没资格拿,或者没拿到!
// 需要优先掌握一个知识,Node中有一个waitStatus的状态
// 1:代表当前Node取消了,不排了,告辞,走人。
// 0:代表默认状态,啥事没有~
// -1:代表当前Node的next节点可能挂起了。
if (shouldParkAfterFailedAcquire(p, node) &&
// 基于Unsafe类的park方法,将当前线程挂起!
parkAndCheckInterrupt())
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// pred是prev
// node是curr
// 拿到上一个Node的状态
int ws = pred.waitStatus;
// 如果上一个Node状态是-1,返回true,代表可以挂起
if (ws == Node.SIGNAL)
return true;
// 上一个节点状态是否是取消状态
if (ws == 1) {
// 绕过状态为1的节点,找到一个状态正常的。
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus == 1);
pred.next = node;
} else {
// 如果上一个节点状态正常,直接修改为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
六、AQS如何释放资源?
- 加锁是执行lock,释放锁资源玩的是unlock方法。
- 释放锁会对state做-1操作,如果-1后state为0,代表释放锁资源成功。
- 如果锁资源释放成功,需要查看head状态是否为-1,如果为-1需要唤醒离head最近的有效节点。
// 释放锁资源
public final boolean release(int arg) {
// 执行tryAcquire释放锁资源
if (tryRelease(arg)) {
// 释放干净了!
// 先拿head
Node h = head;
// 如果head不为null,head的状态是否为 -1
if (h != null && h.waitStatus == -1)
// 唤醒后面挂起的线程(睡觉的线程)
unparkSuccessor(h);
return true;
}
return false;
}
// 释放锁操作。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 释放锁资源的线程必须是持有锁资源的线程,否则甩你一个异常。
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// free代表锁释放干净了么?
boolean free = false;
// 如果state为0,代表锁资源释放干净了
// 没进if,就代表没释放干净
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// 唤醒后续挂起的线程 node是head
private void unparkSuccessor(Node node) {
// 拿到head的状态
int ws = node.waitStatus;
// 状态是-1,归位0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// s可能就是要被唤醒的线程
Node s = node.next;
// 如果s节点出现了问题,不排队了,那就找离head最近的有效节点唤醒!
if (s == null || s.waitStatus > 0) {
s = null;
// 会从tail开始往前找,找到离head最近的有效节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 如果s不为null,代表找到了具体要唤醒的Node
if (s != null)
// 唤醒对应的线程
LockSupport.upnark(s.thread);
}