AbstractQueuedSynchronizer
1. 前言
- AQS是java中管理“锁”的抽象类,依赖于FIFO等待队列,锁的许多公共方法都是在这个类中实现
- AQS锁的类别 – 分为“独占锁”和“共享锁”两种。
- 独占锁 – 锁在一个时间点只能被一个线程锁占有。根据锁的获取机制,它又划分为“公平锁”和“非公平锁”。公平锁,是按照通过CLH等待线程按照先来先得的规则,公平的获取锁;而非公平锁,则当线程要获取锁时,它会无视CLH等待队列而直接获取锁。独占锁的典型实例子是ReentrantLock,此外,ReentrantReadWriteLock.WriteLock也是独占锁。
- 共享锁 – 能被多个线程同时拥有,能被共享的锁。JUC包中的ReentrantReadWriteLock.ReadLock,CyclicBarrier, CountDownLatch和Semaphore都是共享锁。
- 使用AQS作为基础同步器,需要通过更改State状态重写tryAcquire, tryRelease, tryAcquireShared, tryReleaseShared, isHeldExclusively这些方法
2. 源码解析
2.1 数据结构
1 | public abstract class AbstractQueuedSynchronizer |
AQS基于FIFO队列,底层数据结构为双向链表,依据head,tail指针来调度锁的获取和释放。Condition queue 是单向链表,不是必须的,当使用Condition才有此单向链表;可能有多个Condition queue
2.2 内部类
- Node类
1 | static final class Node { |
- ConditionObject
1 | public class ConditionObject implements Condition, java.io.Serializable { |
2.3 独占锁和共享锁
2.3.1 独占锁
具体见ReentrantLock获取锁流程
- acquire() 获取锁
独占模式获取锁,忽略中断
1 | // 获取锁 |
- addWaiter()
1 | // 加入等待队列尾节点 |
1 | // 自旋加入CLH末尾,若CLH为空,则新建一个CLH表头 |
其中,compareAndSetHead和compareAndSetTail 通过CAS方法操作当前线程
1 | //实际调用的UnSafe类 |
- acquireQueued()
acquireQueued()的作用就是“当前线程”会根据公平性原则进行阻塞等待,直到获取锁为止;并且返回当前线程在等待过程中有没有并中断过。
1 | //获取锁 |
1 | // 判断“当前线程是否应该阻塞” |
1 | private final boolean parkAndCheckInterrupt() { |
线程被阻塞之后唤醒,一般有2种情况:
- unpark()唤醒。“前继节点对应的线程”使用完锁之后,通过unpark()方式唤醒当前线程。
- 中断唤醒。其它线程通过interrupt()中断当前线程。
- cancelAcquire()
1 | // 取消继续获取(资源) |
- release() 释放锁
1 | //尝试释放当前线程锁持有的锁。成功的话,则唤醒后继等待线程,并返回true。否则,直接返回false。 |
1 | //唤醒当前线程的后继线程 |
2.3.2 共享锁
具体见ReentrantReadWriteLock.ReadLock
- acquireShared() 获取共享锁
1 | public final void acquireShared(int arg) { |
- doAcquireShared()
doAcquireShared()的作用是在CLH队列中自旋获取共享锁。doAcquireShared()在每一次尝试获取锁时,是通过tryAcquireShared()来执行的!
1 | private void doAcquireShared(int arg) { |
- releaseShared()释放锁
1 | public final boolean releaseShared(int arg) { |
- doAcquireShared
doAcquireShared()的作用是在CLH队列中自旋获取共享锁。doAcquireShared()在每一次尝试获取锁时,是通过tryAcquireShared()来执行的!
1 | private void doAcquireShared(int arg) { |