AQS源码逻辑解析

原理:volatile的state变量,有个CLH队列加cas操作

独占模式:ReenTrankLock

共享模式:计时器,信号量,共享栅栏,读写锁等

什么时候初始化队列:当有线程来抢锁抢不到,然后要进队列的时候,就会初始化。

AQS源码中static静态代码块中封装了很多CAS操作,底层是unsafe的cas方法。

如果有个线程park阻塞,然后interrupt打断这个线程,那么这个线程就再也阻塞不住了,除非把中断标记清除。

流程:线程过来,会先调用tryacquire获取锁,即判断state是不是0,,然后判断队列中有没有元素,如果(公平锁)那么就加入队列,如果第一次或者非公平,那么一来就去抢锁。

如果抢锁失败,那么就要开始排位,acquireQueued里面的addWaiter、

addWaiter方法是把线程加入等待队列

AQS是一个双向链表,节点保存前后指针和当前thread和一个waitStatus:是否要唤醒后一个节点,这个值是由后一个节点来设置。

第一个进入队列的时候初始化,创建一个节点,然后,首尾指针指向它。队首节点的Thread属性永远为null

然后把要加入的节点加入到队尾。(每一个加入的节点都会判断自己是不是队首后一个元素,是的话就会再去tryAcquire获取锁,获取不到就park。park之前会把前一个节点的waitStatus属性设置为-1.

(第一个节点入队他要自旋3次)

小结:

1.在第二个线程加锁时,由于第一个线程已经加锁,所以第一次尝试加锁一定失败

2.由于没有队列,他在入队的时候要先初始化队列

3.加入队列之后有两次循环,每次循环尝试加锁一次,也就是额外自旋2次

4.这两次循环第一次会把前一个节点的ws设置为-1,第二次才阻塞自己

第三个节点:它也拿不到锁,然后要addwaiter进入队列,,把它排到线程二后面的节点。然后进去acquireQueued方法。然后判断自己不是队首第一个元素,他就不会去tryAcquire。(这个tryAcquire也是为了非公平锁的逻辑),然后把前一个节点的ws设置为-1,然后把自己park.

非公平锁和公平锁唯一区别:非公平锁一来就直接去tryAcquire,失败就去排队

公平锁一来先去检查一下队列有没有人,有人就排队,没人再去抢

细节:非公平锁效率高

如果t1拿到锁,它执行结束了,然后需要去唤醒队列中的t2,在唤醒的途中,t3来了,很快就拿到了锁,这时候t2醒了,如果t3执行结束,那t2就直接执行任务,如果t3没结束,t2也会因为tryAcquire失败,从而再次阻塞,只不过这次他就跑到队尾了。(线程的唤醒和阻塞比较耗时)

(以上就是上锁逻辑)

释放锁

tryRelease,state-1,如果减到0,那么就得去队列中看。

ws不为0,表示后面有人需要唤醒。所以state为0之后,就会判断自己的ws是不是不等于0.

释放锁之后,唤醒后面线程,后面线程会判断自己是不是头节点后一个,如果是那么setHead(node),把自己设置成头节点,老的头节点.next=null,便于GC,在setHead中会把当前节点的pre,thread属性设置为null。懂了吧。

也就是说,队列里面的头节点中thread永远为null,即当前持有锁的线程,不会保存在队列中

扩展

如果在lock.lock()之后被interrupe打断会怎么样。

lock会继续阻塞,不会有反应。

为什么:

interrupt原理:仅仅给线程设置了一个中断标记。

这个标记可以被interrupted方法捕获,并去除标记

如果线程park的话,调用interrupt的话,那么确实会唤醒,并且因为中断标记,再也无法阻塞,除非擦除。

所以AQS这么处理的,如果你interrupe,那么线程确实会醒来,然后他会调用interrupted方法擦除状态,然后tryacquire获取锁,因为不是正常唤醒。所以获取锁失败然后入队列阻塞。这里面会有一个变量记录是否被中断过,就是她擦除之后线程阻塞,会有一个线程记录它曾经被中断过,然后等到该线程被正常唤醒的时候,就会去后面selfInterrupt方法中自我中断一下。|=运算符保证标记一旦获取就无法更改

lock()被interrupt小结

1.阻塞中被interrupt自然会醒来

2.但是这个时候属于非正常唤醒,所以先获取并抹除这个线程的interrupt标记,让它在下一个循环过后继续阻塞

3.用|=运算符保证一旦标记被获取就无法覆盖

4.最后线程正常获取锁之后,重新给他interrupt一下

以上是一般的lock.lock()被interrupe的逻辑

lock还有一个可中断的上锁方法

lock.lockInterruptibly();
可中断方法的逻辑

前面逻辑一样,如果节点被唤醒,那么lock()方法中是会用|=记录标记,然后让线程重新入队阻塞

但是在这个方法,它是会抛异常,但是抛异常他会先自己处理,cancelAcquire(node)看名字就是删除的意思。

如果单单抛出异常给用户。那么这个线程还在队列,就会出问题啊,他会去执行自己的方法,因为异常,跳出方法,所以它不会循环,循环也阻塞不住,他会执行自己的线程逻辑,然后等到真正要唤醒它的时候,就会出问题,导致死锁。

因此在这里先捕获异常,自己处理队列,然后再抛异常让用户处理。

那它是怎么处理队列的呢

失效节点的调整

调整思路:让前面的节点跳过这些失效的节点,唤醒后面有效的节点

清除这些失效节点。清除节点的逻辑其实并不是主动清除,而是通过设置参数为null,然后因为头节点出队之后会setHead,这时候就断开了。所以不要怕失效节点导致内存泄漏。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值