title: JUC.AQS
AQS提纲
AQS简介
AbstractQueuedSynchronizer抽象同步简称AQS,它是实现同步器的基础组件,并发包中的锁底层就是通过AQS实现。下面看下AQS类的UML类图
由该图可以看出来AQS是一个FIFO的双向队列,其内部通过Node节点head和tail记录队列的首尾元素。
AQS提供资源独占和共享两种方式,两种方式通过内部的state信息描述线程状态实现独占以及共享。
Node节点内部的SHARED用来标记当前线程获取共享资源被阻塞挂起放入AQS队列。
EXCLUSIVE用来标记线程获取独占资源被挂起放入AQS队列。
waitStatus记录当前线程的等待状态,可以为CANCELLED被取消,SIGNAL线程需要被唤醒,CONDITION线程在条件队列里面等待,PROPAGATE释放共享资源需要通知其它线程节点。
依赖工具类LockSupport
LockSupport是一个工具类,它的主要作用是挂起和唤醒线程,该工具类是创建锁和其它同步类的基础。
LockSupport类是使用Unsafe类实现的,主要方法有park与unpark(Thread thread),也提供定时的方法。
划重点啦,每个使用它的线程都会关联一个许可证,在默认情况下调用是不持有许可证的!
为什么说上面这一段因为当调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用会马上返回不会阻塞。否则会被阻塞挂起,相比较CAS的阻塞要性能高一点。
获取与释放锁
- 子类实现AQS方法
1.tryAcquire(int arg)尝试获取独占锁
2.tryRelease(int arg)释放独占锁
3.tryAcquireShared(int arg) 尝试获取共享锁
4.tryReleaseShared(int arg)释放共享锁 - 独占方式获取锁
当一个线程调用acquire(int arg)方法获取独占锁时,会先调用tryAcquire方法尝试获取资源设置state状态。
成功则返回否则将线程封装成Node节点设置EXCLUSIVE状态插入到AQS队列的尾部,并调用LockSupport.park(this)挂起自己。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 独占方式释放锁
当一个线程调用release(int arg)方法会尝试使用tryRelease操作释放资源,并设置state状态,然后调用LockSupport.unpark(thread)方法激活AQS队列里面
当前释放节点的next节点并尝试调用tryAcquire,查看当前状态变量的state是否满足,满足激活否则挂起放入队列。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//具体CAS修改状态,LockSupport.unpark(s.thread)唤醒next线程Node节点。
return true;
}
return false;
}
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)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
ConditionObject
notify与wait是配合synchronized内置锁实现线程间同步的基础设施一样,条件变量的signal和await方法也是用来配合锁实现线程间同步的基础设施。
它们的不同在于synchronized同时只能与一个共享变量实现同步,而AQS可以对应多个条件变量。
比如生产者和消费者使用ConditionObject可以有两个condition
并且每个ConditionObject都是一个队列存入的是线程Node节点,至于为什么这么做后续在写ReentrantLock或者实现自定义的一个非重入锁的时候写。
我觉得更加便于理解,这一章只是大概描述有这么一个东西。
欢迎扫码加入知识星球继续讨论