AbstractQueuedSynchronizer
是java.util.concurrent.locks中最重要的类。翻译过来是“抽象队列同步器”,我们根据字面来理解:
- Abstract-它是抽象的,业务方根据自己的场景来实现tryAcqurie、tryRelease等方法。例如:CountDownLatch、CyclicBarrier、Semaphore、ReentrantLock等;
- Queue-它维护了一个FIFO的队列,竞争锁的线程都会加入到这个对列中,排队竞争资源;
- Synchronizer-它用来保证在并发情况下,线程安全。
优点:
使用方便:满足日常线程调度的需求,通过CountDownLatch、CyclicBarrier、Semaphore,ReentrantLock很轻松实现了共享锁、公平锁等功能。
性能:在jdk1.6之前synchronized关键字很重,性能很不好(1.6之后引入偏向锁、自旋锁、轻量级锁性能已经替身了许多)
主要逻辑
以ReentrantLock为例,来解释加锁和释放锁的逻辑
加锁
调用acquire方法,对业务进行加锁,acquire流程如下:
- 会调用tryAcquire(1)尝试获取锁,如果失败将当前线程加入到队列中;
- addWatier将当前的线程封装成EXCLUSIVE模式的Node加入到队列中,其中为了避免失败采用了自旋的方法;
- 之后通过自旋acquireQueued获取锁资源,然后根据之前记录的线程是否被中断过的状态,进行线程中断,acquireQueued逻辑如下;
- 获取当前节点的前置节点,如果前置节点是头结点,说明自己可以开始竞争锁资源了,如果成功,将当前节点置为头节点返回;
- 如果当前节点不是头结点,调用shouldParkAfterFailedAcquire判断该节点是否可以进入休眠状态,分支逻辑如下:
- 前置节点如果的waitStatus如果是SIGNAL,说明后续节点可以休息返回ture;
- 如果前置节点的waitStatus是CANCELLED状态(>0),通过循环跳过当前节点前置所有的CANCELLED节点;(之后结合释放锁来看)
- 否则将前置节点的状态CAS改为SIGNAL,acquireQueued下一次自旋,节点就会返回true;
- 如果返回允许挂起后调用parkAndCheckInterrupt(),通过LockSupport.lock()方法挂起当前线程;
- 获取锁资源后,判断线程是否被中断过调用:selfInterrupt()方法来中断线程。
涉及的代码如下
1 | /** |
释放锁
调用acquire方法,对业务进行加锁,acquire流程如下:
调用release方法多锁进行释放,其中逻辑如下,ps:ReentrantLock的公平锁模式会判断是否有前置节点,如果有才会修改state
- 通过调用tryRelease(1)释放锁,成功后取到头head链表的head节点(一般head节点持有的线程都是自己);
- 判断head节点的状态,不为空,同时waitStatus!=0,因为在acqureQueued如果有后置节点已经将他改为SIGNAL状态,所以如果为空说明没有后置节点;
- 调用unparkSuccessor(h),唤醒后置节点,具体逻辑如下:
- 获取头节点位置,见头节点位置将头节点waitStatus置为0;
- 找到后置节点,如果后置节点为空或者是CANCELLED,从尾部开始遍历链表,找到离头节点最近的一个不是CANCELLED状态的节点
- 通过LockSupport.unpark(s.thread)唤醒线程开始竞争锁;
- 这时候这个被唤醒的线程回到acquireQueued的自旋中,走到“p == head && tryAcquire(arg)”这个分支中,即时这时候p!=head,走入shouldParkAfterFailedAcquire方法中,由于这个节点到head之前的节点都赢是CANCELLED对象(见上报从尾部开始遍历链表),所以跳过所有CANCELLED节点,下次必然会p==head,获取锁。
涉及的代码如下
1 | public final boolean release(int arg) { |
总结
AQS利用队列实现了在并发情况下,多线程之间的调度问题;并且合理利用了自旋和挂起笔名了线程之间上下文切换的损耗,和无效的竞争。
其他相关知识
LockSupport对线程阻塞和唤醒(park/unpark)
相比wait和notify的优势
- 不用先用synchronized获取同步锁。
- LockSupport是基于线程对象的,而wait和notify是基于锁对象的,所以不会遇到遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题
源码如下:
1 | public static void park(Object blocker) { |
AQS中FIFO队列介绍,内部类Node
1 | static final class Node { |