1 简介
AbstractQueuedSynchronizer抽象的队列式同步器(抽象类)。提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLock、CountDownLatch等。
其中比较重要的概念有:
- state共享资源
- FIFO
- CAS
- park unpark
2 AQS线程队列
Node是整个队列中最核心的部分,包含CANCELLED、SIGNAL、CONDITION、PROPAGATE四种状态、指向前后节点的指针、thread是节点存储的值。
ReentrantLock中的公平锁与非公平锁都继承了这个抽象类。
)
ReentrantLock包含公平锁和非公平锁,每种锁里面还包含了抽象的锁Sync,抽象锁执行AQS。
如果当前是线程1拥有锁的话,线程2,3,4会调用park操作来被挂起加入队列中。
当拥有锁的Thread1执行完毕后会调用unpark方法,head指向下一个节点。由于ReentrantLock是一个可重入锁,重入次数被AQS.state记录,每重入一次值 + 1,退出一次 - 1。
3 ReentrantLock实例构建
ReentrantLock默认构造函数创建的是非公平锁,如果想要公平锁的话需要在构造方法中传入true。
)
4 多线程抢锁图示(非公平锁)
线程抢到锁的过程使用CAS实现。
初始时等待队列只有一个Head节点,其中存储的Thread是null,用来占位,线程B,C在队列中使用双向链表的方式与Head相连,调用park挂起。
)
上图的分步过程见下文。
4.1 A线程加锁
A线程抢锁时通过CAS操作将state改为1,并将AQS的exclusiveOwnerThread属性设为A,表示当前锁的拥有者。
)
4.2 B线程加锁
此时B线程要进行抢锁,发现state已经是1了,所以CAS操作失败,进入到队列中,waitStatus设置为0
第一次new Node的时候,即创建head,thread设置为了null,waitStatus设置为SIGNAL
)
4.3 C线程加锁
接着C线程抢锁时,操作同B,会将B的waitStatus设置为SIGNAL,自己的是初始化是的值0
)
新插入的节点waitStatus值都设为0原因是用来标记队列中最后一个节点,此时不必再进行对后续节点的unpark操作,等待队列中没有节点时会有一个null的节点用作占位,这两个操作都是防止等待队列中没有等待的线程时而变为空。
4.4 A线程解锁
A线程进行解锁操作之后,B线程可以抢到锁,流程如下:
A线程解锁操作:
- state设为0
- exclusiveOwnerThread设为null表示锁被释放
- head节点向后移动一位进行unpark操作唤醒线程B
- 之前的head节点要被释放
)
4.5 B线程解锁
)
4.6 C线程解锁
)
5 源码分析(以非公平锁为例)
5.1 线程A先抢占锁
构造方法:
1 | java复制代码public ReentrantLock() { |
lock():
1 | java复制代码public void lock() { |
假设现在有两个线程A和B,如下:
1 | java复制代码static final class NonfairSync extends Sync { |
其中acquire(1)方法的源码如下:
1 | java复制代码// 线程B调用:arg = 1 |
尝试抢锁的方法 tryAcquire(arg) 最终调用的:
1 | java复制代码/** |
回到 acquire 代码中 addWaiter 方法:
1 | java复制代码// 线程B:mode = Node.EXCLUSIVE = null |
addWaiter 方法使用到了 Node 的构造方法:
1 | java复制代码// 线程B:thread = Thread.currentThread() mode = null |
对照下面的图来看,nextWaiter 是null,Node的构造方法里没有对 waitStatus 赋值,所以默认为0
)
回到addWaiter方法,其中还用到了enq方法:
1 | java复制代码/** |
此时addWaiter执行完毕,又回到acquire方法,addWaiter返回了承载B的node,现在要执行acquireQueued方法:
)
1 | java复制代码// 线程B:node = 承载B的节点,arg = 1 |
5.2 线程A释放锁
如果A调用unlock操作:
1 | java复制代码public void unlock() { |
释放锁的方法如下:
1 | java复制代码// 线程A:arg = 1 |
其中调用了tryRelease方法:
1 | java复制代码// 线程A:release = 1 |
对应:
)
tryRelease执行完毕之后又回到了release方法中,运行unparkSuccessor方法:
1 | java复制代码private void unparkSuccessor(Node node) { |
释放完毕之后,在线程B的lock方法的一些列调用中有 parkAndCheckInterrupt() 方法,即线程B被挂起了,A释放之后又回到被挂起那个位置继续执行,所以线程B的 acquireQueued() 方法可以抢到锁
对应:
)
本文转载自: 掘金