笔记目录
1.AQS基础 - 抽象队列同步器
AQS
是JUC包下面的一个抽象类,AbstractQueuedSynchronizer
抽象队列同步器,AQS是一个抽象同步框架,它的底层已经实现了并发同步相关的操作,例如阻塞、唤醒等机制。我们一般可以基础AQS或者用内部类的方式来使用它。AQS的特性具有:
- 阻塞等待队列
- 共享模式、独占模式
- 公平锁、非公平锁(非公平效率更高,减少了线程的内核态陷入)
- 可重入
- 可中断
AQS有一个核心变量volatil int state
,这个state表示资源的可用状态,可以获取它,也可以CAS方式设置它。
AQS有两种队列:
- 同步等待队列:所有竞争锁失败的线程入队。
- 条件等待队列:调用await()释放锁资源的线程入队,调用signal()唤醒线程,线程再进入同步等待队列来再次尝试获取锁。
AQS定义了5种队列中Node节点的状态:
1. 值为0,代表节点处于初始化状态,处于同步队列中,等待获取锁。
1. cancelled = 1,表示当前线程被取消。
1. signal = -1,表示当前节点的后继节点线程需要唤醒unpark。
1. condition = -2,表示当前节点在等待condition条件,处于条件队列中。
1. propagate = -3,表示当前状况下,后续调用acuireShared能够执行。
AQS提供的核心抽象方法有:
- tryAcquire():独占,尝试获取锁,成功返回true。
- tryRelease():独占,尝试释放锁,成功返回true。
- tryAcquireShared():共享,尝试获取资源,成功返回>0的正数。
- tryReleaseShared():共享,尝试释放资源,如果允许唤醒后续等待节点,返回true。
1.1 同步等待队列
AQS和synchronized类似,当发生线程竞争时,获取不到锁的线程会进入一个队列。AQS的等待队列结构类似于CLH(Craig、Landin、Hagersten三位大佬发明)一种基于双向链表结构的队列FIFO模式。
- 当线程竞争锁失败时,AQS将这些线程封装成Node节点,放到同步队列中FIFO,并park阻塞线程。
- 当当前节点释放锁后,会唤醒队列的头结点工作,至于是唤醒执行还是抢锁,取决于是否公平。
- 其它线程可以通过调用node节点的signal()、signalAll()可以将条件队列中的节点转移到同步队列中等待唤醒。
1.2 条件等待队列
条件队列基于Condition接口实现,条件接口提供了await()、signal()、signalAll等方法来基于条件的同步唤醒机制,相比于synchronized只有1个条件队列来说,Condition可以支持多个条件,那么就有多个条件队列。
2.ReentrantLock可重入锁
ReentrantLock核心功能基于AQS完成,具有:可中断、可设置超时事件、支持公平/非公平锁、支持多个条件变量、支持重入、加解锁更加灵活。
2.1 和synchronized比较
- 内置锁基于JVM原语,ReentrantLock基于JDK代码层实现。
- 内置锁状态对程序不可见,ReentrantLock锁状态程序可获取、可见。
- 内置锁是非公平,ReentrantLock支持公平和非公平。
- 内置锁不可被中断指令中断,ReentrantLock支持中断#lockInterruptibly()。
- 内置锁发生异常时,自动释放锁,ReentrantLock发生异常不会主动释放,需要自行处理。
- 内置锁的竞争队列是栈FILO结构,ReentrantLock是FIFO。
2.2 #Lock()上锁流程
ReentrantLock内部有一个Sync
内部抽象类其继承了AQS,NonfairSync
和FairSync
两个内部类继承了Sync,提供了公平和非公平的加解锁模式。公平锁在加锁的时候体现在是否立即抢锁,它会判断阻塞队列的存在。非公平就十分的暴力和急迫,在各个阶段都尝试拿锁,是在拿不到,迫不得已入队阻塞。
3.AQS同步等待队列工作原理
3.1 AQS在没有任何线程加锁时
3.2 当T1线程加锁,通过CAS修改state成功
3.3 当T1未释放,T2来加锁,加锁失败、初始化队列、入队、阻塞
3.4 当T1未释放,T3来加锁,加锁失败、入队、阻塞
3.5 T1释放锁,唤醒T2