讲道理,JUC 不只是代码写得好,注释写的也是真的好啊
1. AQS API
AQS 的定位在于实现了一个同步器的框架,能够作为各种同步器的基础。整体来说,这个同步器框架为你提供了一套基础的 API,包括但不限于:
acqure
- 独占式的抢占锁acquireInterruptibly
- 独占式抢占且响应中断异常tryAcquireNanos
- 尝试在特定时间内抢占acquireShared
- 共享式抢占(与独占互斥)release
- 释放抢占的独占锁releaseShared
- 释放抢占的共享锁
这些API 可以用来实现各种锁,但在 AQS 的实现来说,它的实现描述了如何控制:
- 多个线程如何争夺队首来获得同步状态
- 争夺失败的线程如何排队、阻塞和被唤醒
并且,由于它采用了模板设计模式,任何人可以轻易的扩展 AQS 来实现自定义的同步器和锁。
在看同步接口的实现之前,先看同步队列的结构,以及节点如何入队。
2. CLH lock 队列
AQS 内部维护着一个 FIFO 队列,被称为CLH (Craig,Landin,Hagersten)lock 队列。CLH 队列通常用于提供自旋功能,但在 AQS 中被用于实现一个阻塞的同步器。
+-------+ prev +------+ +------+
head | | <----> | Node | <----> | | tail
+-------+ next +------+ +------+
队列中的每个 Node 代表一个线程,线程抢夺失败之后通常就会被放进队里排队等待。
2.1 Node
数据结构 Node
是 CLH queue 中的单元类型:
- 队列里每个
Node
实例都作为一个特定线程的代表,持有一个等待的线程。 - 每个 Node 都包含了一个
waitStatus
字段,这个字段用于跟踪该节点所代表的线程是否应该被阻塞。 - 当前
Node
的前驱节点被释放时,当前节点会收到通知。 - 每个
Node
可能会多次尝试获取锁,但并不保证成功,如果失败了,就可能需要重新等待。
Node
结构
thread
: 节点对应的线程prev
,next
: 前驱节点和后置节点waitStatus
: 标记当前节点对应的线程状态
-SIGNAL
:-1, 表示该节点的后继已经被阻塞或者即将被阻塞。因此,当前节点在解锁释放时,需要对后继节点执行 unpark。
-CONDITION
:-2, 表示当前节点在 condition queue 中。
-PROPAGATE
:-3, 下一次共享式同步状态获取将会无条件地传播下去
-CANCELLED
:1, 表示当前节点已经被取消。
-0
:不是上面的任何一种,是一种默认状态,会在处理的过程中被设置为上面的一种
- 从值得分布来看,当waitStatus
值为非负数,则肯定不需要被唤醒,因此在实际使用的很多时候不需要判断具体的值是多少。
- 所有的修改都通过 CAS 进行。nextWaiter
:TODO
// 构造方法
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
2.2 入队
… 更多,见重读 AbstractQueuedSynchronizer …