1.公平锁和非公平锁区别。
一个获取锁时,直接Cas尝试state,失败再放入等待队列。非公平。
一个获取锁,先放入等待队列。公平。
2.共享锁和独占锁的区别。
共享锁在获取锁时,会cas将共享资源的数量减一。也就是允许多个线程操作共享变量。
独占锁只允许同一时刻只有一个线程占有锁。
3.获取锁流程?
尝试获取锁,判断state是否为0,Cas修改,修改失败调用acquire方法获取,这个方法会调用到自定义的tryacquire方法,设置当前线程为独占线程,失败则addWaiter到同步队列中。
添加到同步队列会先判断是否存在有效节点,没有直接尝试获取锁。
有就拿尾结点,将尾节点作为当前节点的前驱结点。
没有尾节点,会创建一个新的dummy头结点,也作为尾节点。
4.当线程被封装为Node,入队后何时会被唤醒呢?
每一个Node都会做的一个自旋操作。
获取前置节点,是否为头指针,如果是,尝试获取锁,获取锁成功,将自己设置为虚节点。
如果获取锁失败(可能被非公平锁抢了)或者p前置节点不是头结点,判断自己是否需要阻塞。(需要细看)。
在判断自己是否需要阻塞的过程中会把前驱节点是cancel状态的节点删除。
5.cancel状态是怎么来的?
Node在自旋过程中,如果成功获取到锁,会设置failed=false,但是没有获取到锁,并且自旋过程中出现了异常,此时会被finally执行,finally判断failed=true,就把当前节点设置为取消节点。在设置过程中,也会遍历前面的取消节点,找到一个<0的节点,和后面<0的节点连接。
6.如何释放锁?
调用relase方法,调用自定义tryrelases方法,获取当前可重入次数,判断当前线程是否是持有锁线程,是就减一,如果state为0了,那么就设置独占线程为null。tryrelases返回值代表当前这个时刻锁是否没有线程获取了,也就是state应该是0的状态。如果是true,那么会获取head节点,在获取他的waitstaus状态,如果waitstaus==0,说明后继线程正在运行,如果小于0代表被挂起,那么就唤醒。
ReentrantLock有Sync成员变量,还有两个内部类,NotFairSync和FairSync所以初始化时可以选择。
当调用ReentrantLock的lock方法时,实际调用的是Sync的lock方法,非公平会这个时候直接尝试设置state。失败了都调用acquire方法。
acquire是AQS的方法,这个方法会调用自定义类的tryAcquire方法和acquireQueued方法。
ReentrantLock的tryAcquire的方法中在NotFairSync和FairSync加入了可重入的判断逻辑,NotFairSync依旧是先尝试获取state,而FairSync则是会先判断hasQueuedPredecessors()队列是否有有效节点,然后再获取锁。
如果获取锁失败,就会调用AQS的acquireQueued方法。
1 | public final void acquire(int arg) { |
acquireQueued中有每个线程都执行的自旋逻辑:
1.获取前驱节点P。
2.如果p是头结点,那么尝试获取锁。(所以对于同步队列而言,只有在队列虚节点的next才有机会去获取锁)
3.此时可能会被非公平锁抢占。
4.获取锁成功,就把当前节点设置为head,原来节点清空。
5.失败或者非head节点就会判断自己是否需要阻塞。
1 | final boolean acquireQueued(final Node node, int arg) { |
判断方法为如果前置节点是signal(-1)代表资源就绪等待被唤醒,所以当前节点得阻塞。
如果>0代表,前置节点是cancel状态,我们需要帮忙删除这些cancel的节点。并且不需要阻塞进入下一个循环。
如果是0 or PROPAGATE,我们会将前置节点设置为SIGNAL状态,并且也不阻塞,进入下一次循环。
1 | private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { |
在这些过程中出现异常,该node会被设置为cancel状态。
在addWaiter方法中,也会初始化CLH队列,创建虚节点,并且添加当前节点到尾节点。
在设置节点使用CAS的方式。
释放调用unlock到release方法,调用自定义的tryRelease方法。tryRelease做了一些重入的减法操作。返回值为是否释放了锁。
1 | public final boolean release(int arg) { |
unparkSuccessor会将当前node设置为0的状态,以避免阻塞next节点。
除此之外,会尝试把next后所有cancel节点删除,并且唤醒一个非cancel节点。
1 | private void unparkSuccessor(Node node) { |
- 本文作者: 宏
- 本文链接: http://sasuke.top/2024/08/02/AQS/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!