这章不会对Condition部分进行讲解
抽象的队列同步器 AQS
对锁资源获取的过程进行了抽象,具体的获取过程留给子类去实现
子类不用去关心在获取不到锁资源的情况下,当前线程如何被挂起,如何被唤醒
这些都是由AQS来实现,获取资源和释放资源是典型的模板方式模式应用
AQS的重要属性
双向链表来控制锁资源的获取
头节点是获取到锁的线程,thread会被置为null
节点信息
节点的两种模式:共享、独占
节点的五种状态:初始化状态、CANCELLED、SIGNAL、CONDITION、PROPAGATE
CONDITION、PROPAGATE只会在共享模式出现
了解了上面的信息后,我们直接进入今天的重点。
AQS获取独占锁 不可中断的
/**
* 获取独占锁
* tryAcquire 留给需要的子类实现 模板方法模式
* @param arg 需要的资源数量
*/
public final void acquire(int arg) {
//tryAcquire 尝试获取失败
//addWaiter 添加独占模式的节点到线程同步队列
//acquireQueued 会返回当前线程是否需要被标记为中断的 true 将当前线程标记为中断的
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//设置当前线程中断的标志位为true
selfInterrupt();
}
/**
* 尝试获取独占锁
* 具体策略由子类实现
* @return true 获取成功 false 获取失败
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
tryAcquire方法是由具体的锁实现对资源的获取,AQS中protected是让子类有选择的去实现,如果用Abstract子类就必须要去实现它了,不然自己也成了抽象类了。
当tryAcquire获取资源失败了,后续怎么处理呢?别慌,AQS都帮我们实现好了,让我们不用关心,这就是AQS出现的理由。
我们来看看AQS是怎么处理。
当自己实现的tryAcquire获取独占锁失败之后,addWaiter(Node.EXCLUSIVE),会保证将独占模式的节点加入到阻塞队列中。
可以看到死循环(自旋)、CAS用到的很多
acquireQueued
1.如果前驱是头节点就再调用tryAcquire,尝试获取一下锁
2.node是不是需要被挂起且挂起当前线程并返回线程的中断状态
3.继续循环
shouldParkAfterFailedAcquire(p, node),判断node获取锁失败后是不是被挂起。
parkAndCheckInterrupt,挂起当前线程(等待被唤醒)并且会返回线程的中断状态
/**
* 挂起当前线程(等待被唤醒)
* 会返回线程的中断状态
* @return true 当前线程被标记中断的 false 没有被标记中断的
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
//返回当前的线程中断位标志 如果是true的话 会将中断标志重置为false
return Thread.interrupted();
}
这里要注意一下,为什么方法会返回中断状态呢?,是因为有些获取锁的方法是可以被中断的。
获取共享锁
/**
* 获取共享锁
*/
public final void acquireShared(int arg) {
//由子类实现 小于0说明获取失败
if (tryAcquireShared(arg) < 0)
//获取共享锁 AQS实现
doAcquireShared(arg);
}
/**
* 尝试获取共享锁
* 具体策略由子类实现
* @return 返回剩余的可用资源数
*/
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
都是一样的套路,留给子类实现。
doAcquireShared
1.*addWaiter前面已经说过了,只是这里添加的是共享模式的节点
2. 如果是前驱是头节点的话,就尝试获取共享锁,也是调用子类的tryAcquireShared,获取完之后,如果还有可用的资源,就向后传播,并唤醒其它等待锁的节点
3. node是不是需要被挂起且挂起当前线程并返回线程的中断状态
4. 继续循环
setHeadAndPropagate,把node设置同步队列的头节点,如果还有可用的资源数,则继续唤醒后续节点
shouldParkAfterFailedAcquire和parkAndCheckInterrupt**获取独占锁那里有可以看下。
独占锁和共享锁的获取已经说了现在再来说说释放
释放独占锁
/**
* 释放独占锁
* @return true 释放成功 false 释放失败
*/
public final boolean release(int arg) {
//尝试释放独占锁 留给需要的子类实现
if (tryRelease(arg)) {
//独占锁释放成功 唤醒后继节点
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒node的后继节点
unparkSuccessor(h);
return true;
}
return false;
}
/**
* 尝试释放独占锁
* 具体策略由子类实现
* @return true 获取成功 false 获取失败
*/
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
tryRelease,由子类实现。
unparkSuccessor,唤醒node的后继节点。
释放共享锁
/**
* 释放共享锁
*/
public final boolean releaseShared(int arg) {
//尝试释放共享锁成功 子类实现
if (tryReleaseShared(arg)) {
//唤醒后继阻塞节点
doReleaseShared();
return true;
}
return false;
}
doReleaseShared
总结
上面说的获取锁和释放锁都只是其中的一种方式,AQS还提供了,可中断、可超时的获取和释放锁的方式,关于AbstractQueuedSynchronizer完整的源码解析点击这里。后续我会用JUC里面提供的一系列并发工具类来更具体的说明AQS。