AQS
AbstractQueuedSynchronizer 类(简称 AQS)。它是一个抽象类,定义一套多线程访问共享资源的同步器框架,是抽象的队列式的同步器。
同步器是基于模板方法模式的,同步器的子类可以通过继承同步器并实现它的抽象方法来管理同步状态,子类推荐被定义为自定义同步组件的静态内部类。
[TOC]
两个核心
volatile int state
同步的核心其实就一个用volatile修饰的int成员变量,锁的状态就是这个值的更改。0就是当前没有线程获取锁,1是有。可重入锁可以多次加锁,即把state值加一,当然也需要同样次数的解锁,因为0才代表当前没有线程获取锁。
1 | private volatile int state; |
FIFO队列
一个先进先出的双向链表。这个队列的操作有一点复杂,我建议您可以先跳下去看重要方法后,再回头看Node类。
1 | static final class Node { |
重要方法
isHeldExclusively() 该线程是否正在独占资源。只有用到 condition 才需要去实现它。
tryAcquire(int)/tryRelease(int)独占方式,尝试获取/释放资源。
tryAcquireShared(int)/tryReleaseShared(int)共享方式,尝试获取/释放资源。
这篇本博文只介绍独占方式,尝试获取/释放资源的方法。
acquire()获取同步状态
不要小看这一行判断,这一句代码其实就是获取许可的核心操作了。
- tryAcquire尝试获取同步状态,成功就没必要加入队列。
- 如果获取同步状态失败,把线程构造成结点(Node.EXCLUSIVE,独占式)addWaiter把结点加入队列尾部。
- 加入之后acquireQueued()死循环去轮询前一个结点看是否轮到自己了。
- 如果轮到自己了,把自己的线程状态设置为打断等待
1 | public final void acquire(int arg) { |
tryAcquire()尝试获取同步状态
注意:这里的tryAcquire我是把可重入锁的公平锁对tryAcquire()的实现贴过来了!因为AQS抽象类并没有实现这个方法,而是留给子类去实现。
1 | protected final boolean tryAcquire(int acquires) { |
addWaiter()线程构造成结点尾部入队和enq()自旋入队
用CAS结点加入队列
不过当结点被并发地被添加到 LinkedList时, LinkedList将难以保证Node的正确添加,最终的结果可能是节点的数量有偏差,而且顺序也是混乱的。
所以在enq( final Node node)方法中,同步器通过“死循环”来保证节点的正确添加,在“死循环”中只有通过CAS将节点设置成为尾节点之后,当前线程才能从该方法返回,否则当前线程不断地尝试设置。可以看出,enq( final Node node)方法将并发添加节点的请求通过CAS变得“串行化”了。——以上出自《java并发编程的艺术》
1 | private Node addWaiter(Node mode) { |
enq()因为addWaiter中首次添加到队列尾部失败了,自旋加入队列尾部
1 | private Node enq(final Node node) { |
acquireQueued()自旋询问是否到我了
进入一个自旋的过程,不断轮询前面结点的状态,看啥时候到我了。
1 | final boolean acquireQueued(final Node node, int arg) { |
shouldParkAfterFailedAcquire抢锁失败,判断是否需要挂起当前线程
在获取同步状态失败后,线程并不是立马进行阻塞,需要检查该线程的状态。该方法主要靠前驱节点判断当前线程是否应该被阻塞。
- 如果当前线程的前驱节点状态为SINNAL(ws=-1),则表明当前线程需要被阻塞,调用unpark()方法唤醒,直接返回true,当前线程阻塞.
- 如果当前线程的前驱节点状态为CANCELLED(ws > 0),则表明该线程的前驱节点已经等待超时或者被中断了,则需要从CLH队列中将该前驱节点删除掉,直到回溯到前驱节点状态 <= 0 ,返回false
- 如果前驱节点非SINNAL,非CANCELLED,则通过CAS的方式将其前驱节点设置为SINNAL,返回false
1 |
|
如果 shouldParkAfterFailedAcquire(Node pred, Node node) 方法返回true,则调用parkAndCheckInterrupt()方法阻塞当前线程:
1 | private final boolean parkAndCheckInterrupt() { |
接下来以ReentrantLock的源码来分析AQS的具体实现。
ReentrantLock
最核心的成员变量final Sync sync,这是一个继承AQS的抽象类,它有两个实现,一个是公平锁一个是非公平锁。
获取当前占用锁的线程,如果State为0表示当前没有线程获取锁返回null,如果有就getExclusiveOwnerThread获取线程。
1 | final Thread getOwner() { |
公平锁
lock
这里直接把值写死了,每次加锁,acquire方法传入1.
1 | final void lock() { acquire(1);} |
AQS的acquire
点进acquire方法发现它跳到 AQS的acquire方法里去了,而点进AQS的tryAcquire发现它只抛出一个不支持操作的异常。也就是说在公平锁里,加锁这个操作用的AQS的acquire(和非公平锁共用),而tryAcquire是公平锁和非公平锁各自实现的。
1 | public final void acquire(int arg) { |
tryAcquire
这个方法在上文的AQS中已经分析过了,为了方便顺着看,再贴过来。
1 | protected final boolean tryAcquire(int acquires) { |
非公平锁
非公平锁就两方法lock和tryAcquire
lock
第一次不加队列,直接先CAS试图获取锁。没有成功在走AQS的acquire方法
1 | final void lock() { |
AQS的acquire
点进acquire方法发现它跳到 AQS的acquire方法里去了,而点进AQS的tryAcquire发现它只抛出一个不支持操作的异常。也就是说在公平锁里,加锁这个操作用的AQS的acquire(和非公平锁共用),而tryAcquire是公平锁和非公平锁各自实现的。
1 | public final void acquire(int arg) { |
###
tryAcquire
1 | protected final boolean tryAcquire(int acquires) { |
tryRelease()
可重入方式的释放锁
1 | protected final boolean tryRelease(int releases) { |
Synchronized与ReentrantLock的区别
1)互斥锁
2)可重入
3)都保证了可见性和互斥性
两者的不同点:
1)ReentrantLock显示获得、释放锁,synchronized隐式获得释放锁
2)ReentrantLock可响应中断、可轮回,synchronized是不可以响应中断的,为处理锁的不可用性提供了更高的灵活性
3)ReentrantLock是API级别的,synchronized是JVM级别的
4)ReentrantLock可以实现公平锁
5)ReentrantLock通过Condition可以绑定多个条件
6)底层实现不一样, synchronized是同步阻塞,使用的是悲观并发策略,lock是同步非阻塞,采用的是乐观并发策略