AQS浅谈

AQS是抽象同步队列AbstractQueueSynchronizer的简称,AQS定义了一套多线程访问共享资源的同步框架,AQS并不实现任何同步接口,它提供了一些可以被具体实现类直接调用的一些原子操作方法,AQS同时提供了互斥模式和共享模式两种不同的同步逻辑,一般情况下,子类只需要根据需求实现其中一种模式,当然也有同时实现两种模式的同步类,许多类都依赖于它,如reentrantLock等

AQS的工作机制:AQS的等待队列是基于链表实现的FIFO的等待队列,队列每个节点只关心其前驱节点的状态,线程唤醒时只唤醒队头等待线程(即head的后继节点,并且等待状态不大于0)

AQS的主要特点
  1. 内部含有两个队列(Sync Queue,Condition Queue)
  2. AQS内部定义获取锁(acquire),释放锁(release)的主逻辑,子类实现响应的模板方法即可
  3. 支持共享和独占两种模式,共享模式时只用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()函数的具体流程:

  1. 节点进入队尾后,检查状态,找到安全休息点
  2. 调用park()进入waiting状态,等待unpark()或interrupt()唤醒自己
  3. 被唤醒后,看自己是不是有资格能拿到号,如果拿到,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()的流程

  1. tryAcquireShared()尝试获取资源,成功则直接返回
  2. 失败则通过doAcquiredShared()进入等待队列park(),直到被unpark()/interrupt()并成功获取到资源才返回,整个等待过程也是忽略中断的

独占模式下和共享模式的区别

  1. 独占模式和共享模式都是先让线程尝试获取资源(独占模式下是调用tryAquire()方法,共享模式是调用tryAquireShared()方法),获取失败则将线程放入等待队列中(独占模式是调用addWaiter(),共享模式是调用doAquireShared()
  2. 跟独占模式相比,共享模式下只有线程是head.next时,才会去尝试获取资源,有剩余的话还会唤醒之后的线程,如果当前资源不满足head.next线程尝试获取的资源时,它会继续park()等待其他线程释放资源,也不会去唤醒后面的线程执行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值