AQS(AbstractQueuedSynchronizer)初学

ReentrantLock(可重入锁),CountDownLatch(闭包),ReentrantReadWriteLock等内部都是基于AQS实现的,所以有必要学习一下AQS的实现原理:

多线程通过volatile int state(共享资源)的状态来判断是否获取到资源,没获取的线程进入队列,等待被释放资源的线程唤醒。
主要分为独占锁(一个线程获取到资源以后其它线程无法获取资源)和共享锁两种方式(一个线程获取到资源,资源还有剩余允许其它线程获取资源)
AQS类部分代码如下:

private transient volatile Node head;//头节点
private transient volatile Node tail;//尾节点
private volatile int state; //线程通过改变state来判断是否获取资源
AQS中主要的Node的数据结构如下:
static final class Node {
static final Node SHARED = new Node();//共享锁
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;
...略
}
CANCELLED :取消状态
SIGNAL:当node的前驱节点为SIGNAL时,此node节点挂起
CONDITION :(为此状态的时候进入条件队列)
PROPAGATE :(目前不知这个状态的作用)
独占锁 抢占资源的流程:
1.线程尝试获取资源,成功直接返回
2.获取失败加入队尾
3.加入队尾后判断是否再次获取资源(可能加入队尾后,之前的线程已经执行完毕或者中断)
4.判断前驱节点的状态为SIGNAL进入挂起状态
5.挂起之后等待其它线程释放资源来唤醒
//抢占资源的方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(arg):由自定义锁进行实现。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
//尝试使用CAS算法直接将节点加入队尾,允许失败
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//直接使用CAS加入队尾失败
enq(node);
return node;
}
private Node enq(final Node node) {
//使用CAS算法加入队尾 直到成功
for (;;) {
Node t = tail;
if (t == null) { // 队列为空的时候必须进行初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
//队列不为空使用CAS设置队尾,成功返回节点结束循环
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
//插入队尾后,再次进行尝试获取资源(可能之前的线程已经结束)
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;//标识是否失败
try {
boolean interrupted = false;//标识是否中断
//CAS算法
for (;;) {
final Node p = node.predecessor();//获取前驱节点
if (p == head && tryAcquire(arg)) {//当前驱节点是头节点的话再次去获取资源
//获取资源成功,改变头节点的位置,
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//当前结点是否要真的挂起,核对当前线程是否中断
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//当前驱节点的状态为SIGNAL时,当前结点需要挂起
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//前驱节点为SIGNAL直接返回
if (ws == Node.SIGNAL)
return true;
//前驱节点大于0,说明前驱节点已经取消,需要去前面查找与当前结点最近的未取消的节点作为前驱
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//CAS算法设置前驱节点的状态为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//当前线程挂起并判断当前线程是否中断
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

// 独占锁 资源的释放过程:
1.tryRelease(int arg)修改state变量,
2.资源释放成功,唤醒和此节点紧挨的没有被取消的后继节点
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//头节点不为空并且状态不为0 唤醒下一个未取消的节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease(arg):由自定义锁来实现。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
//CAS设置当前结点的状态为0
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//当后继节点为空或后继节点取消时,从队尾查找紧挨着node节点并且未取消的节点,唤醒它
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);
}
共享锁的加锁过程:
1.tryAcquireShared(int )尝试获取资源,成功直接返回,
tryAcquireShared(int ) 返回负值 表示获取失败,0 表示获取成功,并且没有多余资源,正数 表示成功,并且还有可用资源
2.失败调用doAcquireShared()方法加入队列
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
doAcquireShared(int) 源码:
private void doAcquireShared(int arg) {
//加入队列尾部
final Node node = addWaiter(Node.SHARED);
//以下和独占锁的acquireQueued基本相同
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
//尝试获取资源
int r = tryAcquireShared(arg);
if (r >= 0) {//获取成功,setHeadAndPropagate()判断是否唤醒其后的节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//设置头节点
setHead(node);
//判断是否需要唤醒后面的节点(共享资源,有多余的需要唤醒后面的节点)
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
//释放共享资源
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);//唤醒后继节点
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
总结:通过跟随大神的步伐再自己去学习一遍,对AQS有了初步的认识,现在终于知道大学的时候老师说的数据结构是一门很重要的课程这句话的意思,学习之路永无止境,有错之处希望大神指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值