首先这个话题是涉及多线程,所以进行Debug的时候不一定会按照所想的方式进行,所以最好事先有点知识概念,这样会比较好理解AQS的一些设计理念,否则一开始就看源码,会比较难理解,看起来好像写了没多少,其实里面有个设计思路和一些约定在的,我们不是常说约定大于配置么,这里面也有约定,接下去我们慢慢介绍我们需要先理解的一些知识。先声明下,源码是基于JDK11的。
基本总结了这些,可能还有其他的吧,主要方便理解,可能术语不太对:
我们可以看到ReentrantLock
实现了Lock
接口,因为他是悲观锁:
而其中两把锁的尝试获取锁的方法都有这么一段,也就是可重入锁
:
另外可能有些人会说ReentrantLock
是非阻塞的,因为他有自旋,比如这段:
其实我想说,有自旋没错,但是自旋不是一直获取锁资源,而是在尝试第一次获取不到锁资源后,将前驱的状态设置成-1,然后再尝试第二次获取,如果没获取到就直接阻塞了:
因为第一设置前驱状态就是跟前驱打好招呼,如果你要释放锁了,记得叫醒我,我先休息会儿。所以应该是阻塞锁。
顺便提一下,如果前驱在我没有阻塞前就释放锁资源了,把我唤醒了,那调用这个park阻塞是没用的,这个可以研究下LockSupport
的方法,类似于信号量,PV操作。
还有就是在获取锁的过程中无法中断的,就算调用了也不没用,只有被唤醒或者在阻塞的时候被中断了,之后会检查是否整个获取锁期间被中断过,如果有中断的话就会保存,用的是|=
操作,如果中断了,还是会继续自旋,去获取锁资源,获取不到还是要继续阻塞,直到获取为止,感觉就是上了车,就下不了车了,只能到达目的地再说。
另外Thread.interrupted();
这句代码很有意思,为什么是类方法呢,其实就是要获取到是否被中断了,然后又把中断标志位设置false了,这样线程又可以去抢锁资源,否则如果调用实例方法是不会刷新标志位的,这样就变成一个带着中断标志的线程还在抢锁资源,这个就不太合理了。
在获取锁后如果有中断过就会去执行中断方法selfInterrupt
,其实也就是改下中断标志位:
貌似好像改了中断标志位没什么用,其实这个用处是用在你的业务代码:
while (!isInterrupted()){
执行业务代码 ,如果中断了,那也不用执行代码了。这样才算这个线程被中断,什么都没干,只是浪费时间去抢锁资源了。
}
另外当然里面也有自旋的成分,比如加入队列的时候:
可以看到,在创建队列后,就会想加到队列最后,但是此时是多线程可以操作的,所以用CAS原子操作,如果失败了,就继续加,直到加成功为止,没有阻塞的情况。
还有为什么是公平锁,就是因为多了这句代码,不可以插队,要先看有没有人在排队,看起来好像挺简单,其实这句代码还是挺复杂的,得等我们预备知识讲完了后面会讲:
无论是公平锁还是非公平锁,在队列里的结点是要遵守先来先服务的规则:
这个判断前驱结点是否是头结点,才能尝试获取锁,这就说明了,在队列内的结点只有排在头结点后面的那个才可以去获取锁,其他的压根没资格,就算被唤醒了或者被中断了,也没资格去获取锁,还是只能乖乖继续阻塞。这就是保证里队列内部是公平的,所谓的非公平锁,只是说有没在队伍内的新进来的线程在最开始也有尝试获取锁的资格,如果获取到了就是一种插队,所以不公平,但是没获取到,那就排队,等到去排队了,只有排到头结点后面才有资格。
我们先看看这个ReentrantLock
和AQS
到底是什么关系,可以从类图的结构了可以看到,其实ReentrantLock
是实现了Lock
的接口,然后里面拥有Sync
类,而Sync
类才是继承AQS
的,还分为公平和非公平锁,简单来说公平锁就是先来后到,要排队,不公平锁就是可以尝试插队,失败了那就乖乖排队。
我们再来看看大致的结构,可以帮助我们有个大致的概念来理解这个队列同步器是什么,我们来看看AbstractQueuedSynchronizer
的类图:
我们看到头结点和尾节点,应该能大致知道是个双向列表啦,后面的三个大写的属性就是貌似JDK9才有的,就是用来对属性的读写。简单来说就是我有个头,有个尾,还有状态。
简单的介绍了下这个Node
的属性,其实就是双向链表里的结点,既然创建了结点,说明线程要等待啦,所以就需要有等待状态,因为还涉及到是独占的锁还是共享的,是否又设置了条件,所以有几个不怎么好理解的属性,不过没关系,暂时不用管,我们会先讨论简单的,比如独占,无条件的那种。这里补充下,这些结点等待的时候到底做什么呢,其实在自旋,要么获取锁,失败了就阻塞,等待唤醒或者中断,但是唤醒或者中断了还是得去获得锁,获得不到就这样自旋下去了。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。