AQS框架与ReentrantLock

一、AQS:abstractQueuedSynchronizer(抽象队列同步)

1.AQS的作用:既然AQS是一个队列,底层一定是会有FIFO这样等待队列(wait queues)实现的,这个等待的队列的概念就synchronized关键字的底层wait set一样,synchronized是通过调用wait方法使线程进入到等待队列,通过notify/notifyall唤醒,唤醒后进入到Entry set,一个有2个底层的数据结构,而AQS是可以把wait set变成多个,阻塞队列只有一个,这是AQS的基本特点,AQS本身是提供了一个基础组件,基础组件在具体场景下有一种怎么样的表现形式是由子类实现的
2.AQS类由哪些方法构成?
AQS框架与ReentrantLock
AQS框架与ReentrantLock
它的方法有很多,其中一个静态类叫Node,还有一个类叫ConditionObject,这个类实现了Condition接口,所以对与AQS来说, 严格依赖Condition,对Condition来说,是通过Lock.newCondition创建的,对于同一个Lock,只要调用一次Condition就会生成一个Condition对象,这与Synchronized相比是一个改进,synchronized的wait方法被线程调用都会进入到wait set中,而wait set只有一个,对于Condition来说哪怕只有一把锁也有很多Condition对象 ,每一个Condition对象都维护了一个等待队列。
Node是一个双向队列的节点,实际上Node就是封装了在AQS上阻塞的线程,这些线程在FIFO队列内,就可以顺序的进行处理
3.从宏观上看,AQS就是由2部分内容构成的,AQS本身依赖Condition,而对于Lock来说,Condition可以有很多,所以AQS可能依赖很多Condition对象,每个Condition维护着一个等待队列(wait set).还有一个构成部分就是以Node为节点FIFO双向队列,处在Node节点阻塞的线程是阻塞的状态,当处在队列的第一个节点的线程获取到资源就可以去执行,这就涉及到每个Condition下 的wait set中的线程转换到FIFO中阻塞队列的过程,这就跟Synchronized的wait set 与Entry Set转换很相近,只不过Condition维护了多个wait set。
对AQS来说,里面有一个重要的属性决定了当前线程是否有能力拿到AQS的资源,这个属性就是int 类型的state,可以通过AQS生成一个排他锁或者读写锁,都会用到state变量,在不同场景下,state的含义也是不同的
4.ReentrantLock就是使用AQS实现的,在并发包中,有一个规律是如果当前的锁是使用AQS实现的,一般都会有叫sync的成员变量,这个成员变量继承了AQS复写AQS中哪些protect的方法。除了这个ReentrantLock外还有一个就是ReentrantReadWriteLock,就是读写锁,读写锁就是借助了AQS实现的,将State切分为高16位和低16位。

二、ReentrantLock

ReentrantLock主要是由AQS来实现的,下面主要看一下怎么去实现ReenTrantLock?(AQS是框架,知道了ReentrantLock怎么实现可以自己去实现一个同步锁,虽然很难)
一.对于AQS来说,看一下AQS类中重要的方法:
1.TryAcquire方法本质上是获取锁,具体是由子类去实现,它的注释中说明了这种锁的获取是排它锁
2.TryRelease方法是尝试释放的排它锁并且去设置state的值,具体是由子类去实现
3.TryAcquireShared方法是尝试在共享模式下获取锁,具体是由子类去实现
4.TryReleaseShared方法是尝试共享模式下释放锁,具体由子类实现
5.isHeldExclusively方法判断当前的锁是排它锁还是共享锁,具体由子类实现
二、现在看一下ReentrantLock类
1.静态类sync继承了AQS,它将TryRelease、isHeldExclusively等方法实现,对于TryAcquire并没有实现,因为Reentrant来说锁有2种类型,第1种是公平锁,第2种叫非公平锁,公平锁就是队列内有线程,那新加入的线程就排在队列的后面。非公平锁就是Condition已经调用了signal/signalall,线程首先就尝试去获取这把锁,如果获取到了,直接就插队开始执行,如果获取不到就同公平锁的线程一样排队,在ReentrantLock创建的时候可以指定是公平锁还是非公平锁,默认情况下是非公平锁,如果想要使用公平锁传入true即可
2.FairSync(公平锁)继承了Sync,FairSync实现了TryAcquire方法。NonFairSync也继承了Sync,同样实现了TryAcquire,ReentrantLock在创建的时候就有2种类型的锁供我们选择
3.在公平锁中,TryAcquire方法中会获取state的成员变量的值,在排它锁(ReentrantLock的锁都是排它锁)的情形下(或者说定义锁的时候使用了new ReentrantLock,这都是排它锁),state就表示当前线程持有锁的个数,在state等于0的时候,会判断队列内是否有元素并且将state设置为1并且返回true,代表当前线程已经获取到锁,在state不等于0的时候,会判断当前线程是否已经是锁的拥有者,如果是就给当前state加1,并返回true,线程可以继续往下执行,如果不是锁的拥有者,直接返回false
4.在非公平锁中,tryAcquire方法调用了nonfairTryAcquire方法,它的实现与公平锁中TryAcquire的实现只是少了一个判断,这个判断就是"检查队列内是否有元素的判断"是没有的,其它全部相同
5.现在再来看一下unlock代码做了什么,它调用了Sync中的TryRelease方法将state的值减1,当state的值等于0的时候,释放掉排它锁的线程,对于锁的释放,公平锁和非公平锁都是使用了TryRelease这个方法
6.总结一下:
对于ReentrantLock来说,其执行逻辑如下所示:
壹:尝试获取锁的对象,如果获取不到(意味着其它线程已经持有了锁,尚未释放),那么它就会进入到AQS的阻塞队列中
贰:如果获取到,根据锁是公平锁还是非公平锁来进行不同的处理,如果是公平锁,线程会直接放到AQS阻塞队列的末尾,如果是非公平锁,那么会尝试进行cas计算,如果成功,则会直接获取到锁,如果失败,则与公平锁的处理方式一样
叁:当锁被释放时(调用了unlock方法),那么底层会调用release方法对state进行减1操作,如果减1后,state值不为0,则release操作就执行完毕,如果减1操作后,state值为0,则调用LockSupport的unpark方法唤醒该线程后等待队列中的第一个后继线程,使之能够获取到对象的锁(release时,对于公平锁和非公平锁处理逻辑都是一致的),之所以调用release方法后state值不为0,原因在于ReentrantLock是可重入锁,表示线程可以多次调用lock方法,state值都会加1
Ⅳ:对于ReentrantLock来说,所谓的上锁,就是对AQS中state成员变量加1,释放锁就是对state减1