文章目录
AQS介绍
AbstractQueuedSynchronizer
,简称AQS, 提供了一种实现同步器的框架. 该框架提供可以阻塞的加锁和释放锁. 其底层依赖FIFO双向链表.
在介绍AQS之前,首先对锁进行简单的介绍. 按照不同维度, 对锁进行简单划分:
- 独占锁/共享锁
- 可重入锁/不可重入锁
- 公平锁/非公平锁
基于AQS框架, 可以实现上面提及的锁, 而AQS的作用是降低实现的复杂度.
锁的状态
在AQS中, 保存着一个int类型的变量, 该变量是用来标识锁的状态. 注意该变量是一个int类型的值, 不是boolean类型. 所以这个锁的状态并不是只有两种状态(0没获取,1获取), 而是可以有多种状态, 比如表示可以允许几个线程获取锁或按位标识锁的状态, 比如前多少位表示读的状态, 后多少位表示写的状态(ReentrantReadWriteLock).
先看下源码中出现该state的位置:
分析源码得知, state变量是私有的, 实现AQS者不能直接访问, 而访问的方式只有通过getState()
和 setState(int)
.
双向链表介绍
在AQS底层封装了双向链表, 该链表用来保存等待获取锁的线程. 每一个节点代表一个线程, 并且每个节点都有等待的状态. 当有线程想要获取锁, AQS会将线程封装成节点(Node),然后加入到队列中.
正常情况下双向链表如下:
入队时会将新加入的Node添加到链表尾部,并且设置新的队尾. 设置队尾通过CAS操作,只有通过CAS设置队尾成功,才会更新前一个节点的后继. 在入队操作中有个特殊的情况是队列未初始化(tail == null
),这种情况需要先将队列初始化.
下面来介绍两个涉及入队的方法, enq
和addWaiter
.
// 节点入队的代码.
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
// 初始化双向链表, 可能失败.
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 加入到双向链表尾, 可能失败.
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
代码中需要注意的点:
- 入队时, 如果发现双向链表未初始化, 则会先进行初始化.
- 双向链表中表头初始有一个哨兵节点, 这个哨兵节点后续会被移除.
- compareAndSetHead/Tail 操作是原子操作, 能够保证多线程情况下, 若与期望不相等, 则设置失败.并且返回false.
下图代表双向链表初始化.
addWaiter
是对enq
的一个封装, 它会尝试添加到队尾,如果失败了之后, 才会降为调用enq
方法. 另外 addWaiter
方法还有一个重要的功能, 是将当前线程封装为节点Node.
// 以给定的mode添加一个node. 该node会把当前线程封装进去.
private Node addWaiter(Node mode) {
// 根据节点的mode创建node
Node node = new Node(Thread.currentThread(), mode);
// 尝试将新加入的node添加到队尾,
Node pred = tail;
if (pred != null)