大纲
AQS
什么是AQS
- java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列,条件队列,独占获取,共享获取等,而这些行为的抽象就是基于AbstractQueuedSynchronizer(简称AQS)实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器
- JDK中大多数的同步器如Lock,Latch,Barrier等,都是基于AQS框架来实现的
- 一般是通过一个内部类Sync继承AQS
- 将同步器所有调用都来映射到Sync对应的方法
自己总结的回答如下:
AQS全程为AbstractQueueSynchroinizer,是一个抽象的队列同步器,像JDK中大多数同步器Lock,Latch,Barrier等都是基于AQS来实现的。
AQS是基于MESA模型实现的抽象类,内部管理着两种队列,一种是基于双向链表的同步等待队列,另一种是基于单向链表的条件队列。
AQS按照资源共享模式也有着两大类的实现,基于独占(Exclusive)资源的ReentrantLock,以及基于共享(Share)资源的Semaphore/CountDownLatch。
AQS也是模板方法模式的一个很经典的体现,有关出队、入队的相关细节AQS已经帮我们实现,而有关加锁、解锁的操作AQS只提供抽象方法,需要子类具体去实现
AQS相关特性
AQS具备的特性
- 阻塞等待队列
- 共享/独占
- 公平/非公平
- 可重入
- 允许中断
AQS内部维护属性
volatile int state
标识资源的可用状态
State三种访问方式
getState()
setState()
compareAndSetState()
AQS定义两种资源共享方式
Exclusive-独占,只有一个线程能执行,如ReentrantLock
Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
AQS定义两种队列
- 同步等待队列:主要用于维护获取锁失败时入队的线程
- 条件等待队列:调用await()的时候会释放锁,然后线程会加入到条件队列,调用signal()唤醒的时候会把条件队列中的线程节点移动到同步队列中,等待再次获取锁
AQS定义了5个队列中节点状态
0(初始值),标识当前节点在sync队列中,等待着获取锁
1(CANCELLED),表示当前的线程被取消
-1(SIGNAL),表示当前节点的后继节点包含的线程需要运行,也就是unpark
-2(CONDITION),表示当前节点在等待condition,也就是在condition队列中
-3(PROPAGATE),表示当前场景下后续的acquireShared能够得以执行
同步等待队列
同步等待队列是基于双向列表数据结构的队列,是FIFO先进先出线程等待队列
也称为CLH队列(由三个人首字母命名),JAVA中CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制
AQS依赖CLH同步队列来完成同步状态的管理:
当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程
当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态
通过signal或signalAll将条件队列中的节点转移到同步队列(由条件队列转化为同步队列)
条件等待队列
条件队列是基于单向链表保存的,用nextWaiter来连接:
调用await方法阻塞线程
当前线程存在于同步队列的头结点,调用await方法进行阻塞(从同步队列转化到条件队列)
Condition接口详解
- 调用Condition#await方法会释放当前持有的锁,然后阻塞当前线程,同时向Condition队列尾部添加一个节点,所以调用Condition#await方法的时候必须持有锁。
- 调用Condition#signal方法会将Condition队列的首节点移动到阻塞队列尾部,然后唤醒因调用Condition#await方法而阻塞的线程(唤醒之后这个线程就可以去竞争锁了),所以调用Condition#signal方法的时候必须持有锁,持有锁的线程唤醒被因调用Condition#await方法而阻塞的线程
ReentrantLock
ReentrantLock是一种基于AQS框架的应用实现,是JDK中一种线程并发访问的同步手段,它的功能类似于synchronized,是一种互斥锁,可以保证线程安全
与synchronized区别
- synchronized是JVM层次的所实现,ReentrantLcok是JDK层次的实现;
- synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过 ReentrantLock#isLocked判断;
- synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;
- synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以被中断的;
- 在发生异常时synchronized会自动释放锁,而ReentrantLock需要开发者在finally块中显示释放锁;
- ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活;
- synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁(回顾一下sychronized的唤醒策略),而ReentrantLock对于已经在等待的线程是先来的线程先获得锁;
ReentrantLock的使用
ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
ReentrantLock lock = new ReentrantLock(true); //公平锁
//加锁
lock.lock();
try {
//临界区
} finally {
// 解锁
lock.unlock();
}
ReentrantLock源码分析
有关源码,我还会具体出一篇博客一步步看ReentrantLock是如何实现的,尽情期待~
关注点:
- ReentrantLock加锁解锁的逻辑
- 公平和非公平,可重入锁的实现
- 线程竞争锁失败入队阻塞逻辑和获取锁的线程释放锁唤醒阻塞线程竞争锁的逻辑实现 ( 设计 的精髓:并发场景下入队和出队操作)
两个for循环,保证一定能入队以及一定能获取锁