5.AQS如何释放资源的呢?
有获取资源就有释放资源的反过程,这样程序顺利的实现我们想要的效果。那么AQS是如何释放资源的呢?AQS释放资源的方式也分realse(独占释放资源)和realseShared(共享释放资源)两种。
(1)realse(释放独占资源)
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
在release方法中,首先调用tryRelease去释放资源,当然tryRelease方法也是交由子类去继承重写的。如果释放资源成功的话则去调用unparkSuccessor去唤醒后续的线程。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
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);
}
在unpark方法中,首先去判断线程的状态,如果线程的状态值小于0的话,则去更改其状态,然后获取到后面的节点,如果后面的节点是空或者已经是取消的状态,则从同步队列队尾想队头方向一直找,直到找到一个不为空且状态小于等于0的节点,然后唤醒他。
(2)realseShared(释放共享资源)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
releaseShared和release差不多,先调用tryReleaseShared尝试释放资源,tryReleaseShared也是由继承的子类实现的。然后调用doReleaseShared去唤醒后续线程,这和获取共享资源差不多,因为如果共享资源还可以获取的话,在获取共享资源后也会调用doReleaseShared去唤醒后续的线程来获取资源。
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;
}
}
在doReleaseShared中,先判断头节点的状态,如果是SIGNAL的话则说明后续的节点需要被唤醒,则将头节点的状态改为0,然后去唤醒后序的节点。
为什么没有看到把头节点给移除的操作呢?其实移除和添加头节点操作是在获取资源的时候去操作的,因为一个方法中去进行这两个操作,避免了在两个方法中进行操作带来的线程安全与cpu消耗的问题。
6.ConditionObject
在AQS中也有一个内部类ConditionObject,叫条件变量,他是配合锁来使用的。一个锁可以拥有许多条件变量,每个条件变量都有一个条件队列。
每个条件变量都是有其对应的锁来创建出来的,在调用conditionObject方法的时候首先要获取锁才能去调用。如果调用condition的await方法时,会创建一个node节点添加到条件队列中去,并且当前线程会释放锁。而当其他线程调用该condition的signal或者signalAll(首先也得先获得锁)会去条件队列中唤醒一个或者所有的线程,然后重新加入到同步队列中去等待获取锁。
这个场景就好像是我们去银行排队(相当于同步队列)存款,到我们办理业务的时候突然发现有的人忘记填表,有的人忘记开户了,所以这些人得先把办理的时间交给下一个人(await),然后有的去填表处排队(相当于条件队列),有的去开户处排队(相当于条件队列),然后你办好之后这些之后,那个已经在办理存款的人告诉你或者告诉那些去填表和开户的人说你们可以重新排队办理存款了(signal或者signalAll),于是重新去排队等待存款(重新加入同步队列)。
总结:AQS设计的好处在于用了state代表了资源,然后具体如何获取或者释放资源的操作交给具体的子类去实现。而如何去设计同步队列和条件队列,加入、唤醒和移除节点这些复杂的操作子类则完全不用关心。