AQS框架与ReentrantLock、ReentrantReadWriteLock

一、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类由哪些方法构成?
在这里插入图片描述
在这里插入图片描述
它的方法有很多,其中一个静态类叫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

三、ReentrantReadWriteLock

1.ReentrantReadWriteLock方法中传入true表示公平锁、默认就是非公平锁,这与ReentrantLock是一样的
2.所谓读写锁,对于大多数业务场景来说读是不需要加锁的,只有写需要加锁,我们把下面代码的读锁换成写锁后就能够看到打印结果会变慢,基本上与ReentrantLock的速度一样,对于之前的Reentrant来说,它是典型的排它锁,不管是读操作还是 写操作都会加锁,对于ReentrantreadwriteLock来说,如果第一个线程是获取读锁,第二个线程是可以获取到读锁的。如果第 一个线程获取到写锁,其它的线程只能等待
3.通过1知道ReentrantReadWriteLock有公平锁和非公平锁,在公平锁下又可以分为readlock和writelock,在非公平锁下也可以分为readlock和writelock,现在我们来查看一下非公平锁中readLock方法,在这个方法中调用了acquireShare的方法,表示获取共享锁,在acquireShare方法中,调用了tryacquireshared方法,方法的流程是当前对象有没有被上写锁,如果有,当前线程还不是已经上锁的线程,直接返回,如果没有上写锁,上读锁就一定可以成功,如果没有其它线程上读锁,就置为1,如果有其它线程上读锁,就做加操作。在来看一下,在非公平锁中writelock,在这个方法中调用sync的acquire方法,在acquire方法中,调用了Tryacquire方法,方法流程是如果读锁数量和写锁数量不为0并且持有者不是当前线程,直接失败,如果当前state下16位的值等于0,那么可以直接上写锁
4.关于ReentrantReadWriteLock的操作逻辑:
读锁
①:在获取读锁的时候会尝试先判断当前对象是否有写锁,如果有,直接失败
②:如果没有写锁,就表示当前对象没有排它锁,则当前线程会尝试给对象加锁
③:如果当前线程已经持有了该对象的锁,那么直接将读锁数量加1
④:因为读锁是共享锁,所以可以有很多线程对同一个对象加读锁
写锁
①:在获取写锁的时,会尝试判断当前对象是否拥有了锁(读锁与写锁),如果已经拥有且持有的线程并非当前线程,直接失败
②:如果当前对象没有被加锁,那么写锁就会为当前对象上锁,并且将写锁的个数加1
③:将当前对象的排它锁线程持有者设为自己
5.非公平锁中读锁的释放(readLock.unlock()),unlock调用了Sync的releaseShared方法,releaseShared调用了TryReleaseShared方法
6.这个代码中并没有使用Lock创建condition,所以也没有使用condition的await和signal/signalall方法,那么就不存在condition的队列,在这个代码中只是使用了AQS的阻塞队列

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值