AQS是抽象同步队列AbstractQueueSynchronizer的简称,AQS定义了一套多线程访问共享资源的同步框架,AQS并不实现任何同步接口,它提供了一些可以被具体实现类直接调用的一些原子操作方法,AQS同时提供了互斥模式和共享模式两种不同的同步逻辑,一般情况下,子类只需要根据需求实现其中一种模式,当然也有同时实现两种模式的同步类,许多类都依赖于它,如reentrantLock等
AQS的工作机制:AQS的等待队列是基于链表实现的FIFO的等待队列,队列每个节点只关心其前驱节点的状态,线程唤醒时只唤醒队头等待线程(即head的后继节点,并且等待状态不大于0)
AQS的主要特点
- 内部含有两个队列(Sync Queue,Condition Queue)
- AQS内部定义获取锁(acquire),释放锁(release)的主逻辑,子类实现响应的模板方法即可
- 支持共享和独占两种模式,共享模式时只用Sync Queue,独占模式有时只用Sync Queue,但若实际Condition,则还有Condition Queue
AQS源码分析
AQS中的队列节点类型为Node,内部主要有几个字段:
SHARED:用来标记该线程是获取共享资源时被阻塞放进AQS队列的
EXCLUSIVE:用来标记线程是获取独占资源时被挂起后放入AQS队列的
waitStatus:记录当前线程等待状态
其中当前线程等待状态又可以为:
(1)CANCELLED:线程被取消了
(2)CONDITION:线程在条件队列里面等待
(3)SIGNAL:线程被唤醒
(4)PROPAGATE:释放共享资源时需要通知其他节点
volatile Thread thread;:存放进入AQS队列中的线程
state状态
AQS维护了一个volatile int类型的变量,表示当前同步状态
独占模式下的方法
1.acquire()方法
acquire是一种以独占方式方式获取资源,且忽略中断,如果获取到该资源,线程直接返回,否则进入等待队列,直到获取到资源为止,该方法是独占模式下线程获取共享资源的顶层入口,acquire函数流程如下:
- tryAcquire()尝试直接去获取资源,如果成功则返回
- addWaiter()将该线程加入等待队列的尾部,并标记为独占模式
- acquiredQueued()使线程在等待队列中获取资源,一直获取到资源后才返回,如果在整个过程中被中断过,则返回true,否则返回false
- 如果线程在等待过程中被中断过,它是不响应的,只有获取资源后才进行自我中断
1.1tryAcquire()方法
tryAcquire尝试以独占的方式获取资源,如果获取成功,则直接返回true,否则直接返回false。该方法的默认实现是抛出一个异常,具体实现由自定义的扩展了AQS的同步类实现,AQS只负责定义了一个公共的方法框架,之所以没有定义成抽象方法,是因为独占模式和共享模式下实现的方式不同,如果都定义成abstract,那么每个模式也要去实现另外一模式下的接口
1.2addWaiter(Node)
该方法将当前线程根据不同的模式(Node.EXCLUSIVE互斥模式、Node.SHARED共享模式)加入到等待队列,并返回当前线程所在的节点。
addWaiter分为两种情况:如果队列不为空,则以通过compareAndSetTail方法以CAS的方式将当前线程节点加入到等待队列的末尾(因为在多线程环境下,往一个队列放元素,也可能会出现并发问题)。如果队列为空,则通过初始化一个等待队列,并返回当前节点
1.3acquireQueued(Node,int)
acquireQueued()用于队列中的线程自旋地以独占且不可中断的方式获取同步状态,直到拿到锁之后再返回。该方法的实现分成两部分:如果当前节点已经成为头节点,尝试获取锁(tryAcquire)成功,然后返回,否则检查当前节点是否应该被park,然后将该线程park并且检查当前线程是否可以被中断
acquiredQueued()函数的具体流程:
- 节点进入队尾后,检查状态,找到安全休息点
- 调用park()进入waiting状态,等待unpark()或interrupt()唤醒自己
- 被唤醒后,看自己是不是有资格能拿到号,如果拿到,head指向当前节点,并返回从入队到拿到号的整个过程是否被中断过,如果没有拿到,继续流程1
总结一下acquire()的流程
- 调用自定义同步器的tryAcquire()尝试去获取资源,如果成功则直接返回
- 没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式
- acquireQueued()使线程在等待队列中休息,有机会轮到自己时(即会被unpark())会去尝试获取资源,获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false
- 如果线程在等待过程中被中断过,它是不响应的。只是在获取资源后才进行自我中断selfInterrupt(),将中断补上
2.release(int)方法
release(int)方法是独占模式下线程释放共享资源的顶层入口,它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源
与acquire()方法中的tryAcquire()类似,tryRelease()方法也是需要独占模式的自定义同步器去实现的,正常来说,tryRelease()都会成功的,因为这是独占模式,该线程释放资源,那么它肯定拿到独占资源了,直接减掉相应量的资源即可,也不需要考虑线程安全的问题
unparkSuccessor(Node)方法用于唤醒等待队列中下一个线程,需要注意的是下一个线程并不一定是当前节点的next节点,而是下一个可以用来唤醒的线程,如果这个接待你存在,调用unpark()方法唤醒
共享模式下的方法
1.acquireShared(int)
acquireShared(int)方法是共享模式下线程获取共享资源的顶层入口,它会获取指定量的资源,获取成功则直接返回,获取失败则进入等待队列,直到获取到资源为止,整个过程中忽略中断
1.1aquireShared()的流程
- tryAcquireShared()尝试获取资源,成功则直接返回
- 失败则通过doAcquiredShared()进入等待队列park(),直到被unpark()/interrupt()并成功获取到资源才返回,整个等待过程也是忽略中断的
独占模式下和共享模式的区别
- 独占模式和共享模式都是先让线程尝试获取资源(独占模式下是调用tryAquire()方法,共享模式是调用tryAquireShared()方法),获取失败则将线程放入等待队列中(独占模式是调用addWaiter(),共享模式是调用doAquireShared())
- 跟独占模式相比,共享模式下只有线程是head.next时,才会去尝试获取资源,有剩余的话还会唤醒之后的线程,如果当前资源不满足head.next线程尝试获取的资源时,它会继续park()等待其他线程释放资源,也不会去唤醒后面的线程执行