了解的不多 写博客主要是为了便于自己理解和记忆 内容多数为各个博客上面总结的 再加上一点自己的理解 见谅
什么是AQS?
AbstractQueuedSynchronizer 抽象队列同步器 是一套多线程访问共享资源的框架 注意是框架 不是具体的实现
原理:内部依赖了FIFO双向队列 如果当前线程获取失败 AQS会将该线程以及等待状态等信息构造成一个Node,将其加入同步队列的尾部,同时阻塞当前线程,当同步状态释放时,唤醒队列的头节点
具体的实现有什么?
如CountDownLatch ReentrantLock等 我们以ReentrantLock为例
ReentrantLock:
先看一下结构
ReentrantLock有公平锁和非公平锁 具体可以通过构造方法选择 这里我们以公平锁为例:
lock:
一路点进来我们发现lock方法最终是这个样子的:
先说一下各个方法的作用:
- tryAcquire:尝试获取资源 如果成功 直接返回
- addWaiter :入队 将线程加入等待队列的尾部 并且会标记成独占模式
- acquireQueued : 让等待的线程获取资源 如果等待的过程被中断则返回true
这里需要注意的是源码里面有很多取反的操作 比如tryAcquire()方法 如果没有获取到了资源 则返回false 取反后是true 这里是&& 如果没有获取到资源 刚好返回true 继续往下执行 不要弄混
tryAcquire:
首先获取当前线程和state状态码 然后进行判断状态码是否为空 我们假设现在是刚开是的状态 state自然为0 然后判断是否需要排队(hasQueuePredecessors() )因为假设是最开始的状态 肯定不需要排队 所以返回false 取反是true 往下执行 compareAndSetState() 来设置state 如果设置成功则执行setExclusiveOwnerThread(current)来设置当前线程持有这把锁
如果不是最开始的情况 即获取资源失败 则开始执行后续的方法
addWaiter()
这里也分为不同的情况 我们先假设是最开始的时候 队列里面什么都没有 所以pred必然为null 执行enq方法
enq方法上来就是个死循环 获取了节点的尾部 (此时尾部还是null)
然后设置了节点的头部
然后把尾部赋值给头部
注意:这里整个AQS队列的对头所对应的node里面的Thread永远为null
然后开始执行第二次次入队 因为队列里面已经有一个Node了 所以这次prev不为空了 就会执行下面的方法
核心思想就是想让这个新的Node入队嘛 所以就会判断链表尾部是不是pred 如果是就改成node 然后把尾部指向新的Node 新的Node的prev和旧的next互相指向
入队成功后执行acquireQueued方法:
上来的两个boolean值表示是否成功拿到资源 是否被中断过
我们发现又是一个for(;????
先拿到前驱 这里有两种情况:
1.如果前驱是头部
2.不是头部
如果是第一种情况 它就会尝试再次获取资源 tryAcquire() 这里有一个概率很小的事情 就是说我在尝试获取资源的时候 我前面的节点刚好释放掉锁了 然后我顺势拿到锁 拿到锁之后 前一个节点得出队 怎么出队?就是把它的prev和next都设置成null 然后返回interrupted
再往下执行:
如果前一个节点不是头部或者说是头部但是没有释放资源 则就会调用shouldPakrAfterFailerAcquire()方法判断现在能不能休息(park) 如果可以休息我们就直接park掉
parkAndCheckInterrupt()这个方法就是真正执行park的方法
shouldParkAfterFailedAcquire
最开始就拿到前驱的状态开始判断
如果state>0说明是不正常的状态
这里有个do while循环 意思是如果说前驱放弃了 那就一直往前找 直到找到一个正常的节点 然后加塞到他的后面 剩下的形成无引用链 稍后会被GC
acquire() 流程总结:
1.调用tryAcquire()方法尝试获取资源,这个方法需要同步器自己实现,体现了非公平锁,如果拿到资源则直接返回,进行业务逻辑处理。
2.没有拿到资源则调用addWaiter()将线程加入等待队列尾部,并标记为独占模式。
3.acquireQueue:使线程在等待队列中等待,有机会时(有机会或被unpark)会去尝试获取资源。获取到资源才返回,如果在等待过程中被中断,则返回false,否则返回true
4.如果线程在等待过程中被中断,它是不会响应的,只有获取资源后,才进行自我中断,将中断补上