知识点
AQS维护着两个队列:
- 一个是由AQS类维护的CLH队列(用于运行CLH算法)
- 另一个是由AQS的内部类ConditionObject维护的Condition队列(用于支持线程间的同步,提供await,signal,signalAll方法)。
关于锁
- 锁的释放:通过持有锁的线程对CLH队列的下个节点调用
LockSupport.unpark(s.thread);
- 加锁:调用
LockSupport.park(this);
将本线程阻塞
Node.SIGNAL
有2个作用:
- 前继节点为
SIGNAL
时,后继节点会被挂起- 前继节点释放锁或被取消之后,必须唤醒(unparking)其后继结点
共享模式和独占模式在代码上的区分点:
doAcquireShared(int arg)中:
int r = tryAcquireShared(arg); //r说明可以多线程共享锁
setHeadAndPropagate(node, r); //显示当本节点获得锁后,还要通知后续节点继续获取锁
AQS中对CLH算法的实现与标准的CLH算法有什么异同?
到这里已经可以解答这个问题了。AQS到底在哪些地方变种
CLH锁算法?
- CLH是一种自旋锁算法(在得到锁之前会不停地自旋),而AQS会在几次自旋失败后就将线程阻塞,这是为了避免不必要地占用CPU;
- CLH是自旋在前继节点的标志位上的,而AQS是自旋在
p == head
上面(即不停地判断前继节点是否是头节点),只有在发现前继节点是头节点时,才会通过tryAcquire
尝试获得锁,这里有一个比较另我困惑的地方,就是head是一个volatile的全局引用,这么做的话显然违背了CLH锁的Local Spin的思想,具体原因未知,可能是因为AQS最初就是被设计为阻塞的同步器而不是自旋锁吧。
Condition
Node类的nextWaiter
字段其实是用来存放Condition队列的后继的,要和next
字段(用来存放CLH队列后继)进行区分。
之所以Condition队列和CLH队列都采用Node类作为节点的原因就是为了方便将节点从Condition队列搬运到CLH队列。