栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

从ReentrantLock的角度思考AQS

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

从ReentrantLock的角度思考AQS

我们上一篇简单介绍了AQS这个技术点,这一篇我们从ReentrantLock这个锁的角度来分析A Q S ,帮助大家理解

从源码开始

首先,我们先看一下ReentrantLock的内部的抽象类Sync,这个是继承于AQS的,重写了其中的一些方法,我 们会在下面源码中解析,继续往下看,记住这个Sync

我们知道这个锁可以实现公平锁和非公平锁,我们来看下是如何实现的


上面的是非公平锁,下面的是公平锁,默认的是非公平锁,我们看下非公平锁的实现是先通过CAS的方式 去加锁,加锁成功之后就将当前线程设置为活跃的持有锁的线程

1 
4	private transient Thread exclusiveOwnerThread;

失败的话会执行acquire方法,OK,这里我们再看下公平锁FairSync的lock方法的实现,这个公平锁没有 像上面非公平锁那样判断,而是直接调用了acquire方法

这里大家应该也懂了非公平锁和公平锁的真正区别了吧,就是非公平锁的时候,线程来的时候会多一次直 接尝试加锁,剩下的操作就是一样了

OK,让我们进去acquire方法看

看一下try Acquire方法

可以看出,这里只是AQS的简单实现,具体获取锁的实现方法是由各自的公平锁和非公平锁单独实现的(以ReentrantLock为例)

如果该方法返回了True,则说明当前线程获取锁成功,就不用往后执行了;如果获取失败,就需要加入到 等待队列中。下面会详细解释线程是何时以及怎样被加入进等待队列中的。

O K , 知道了这个我们就得看看ReentrantLock是如何实现try Acquire方法的老规矩,先看一下非公平锁中的具体实现


大家看代码应该也比较好理解,第一步先判断state==0,这个0也就意味着这个共享资源处于空闲状态, 于是这里就会先尝试去抢一下锁,假如此时等待队列中有等待线程,则就是等待线程中的第二个节点和这个新 加入的这个线程去抢这个锁了

为什么是第二个, 因为第一个h e a d 节点存储的永远是占用锁的线程节点Node

接下来就是判断当前持有锁的线程和当前线程是否是同一个,如果是同一个,则将state+1,这里就 是ReentrantLock支持重入性的关键,到时候解锁的时候也是通过减去这个state计数的

抢到锁或者重入锁,都会返回true,返回true,加锁方法就直接加锁了

如果既没抢到锁,又发现占用锁的线程不是当前线程,则返回false,继续执行上面这是非公平锁的tryAcquire方法,接下来咱再看这个公平锁的tryAcquire方法

这个也是先判断状态是否为0,这个的==0之后的处理逻辑就很明了了,直接通过hasQueuedPredecessors方 法判断队列中是否有等待的节点,如果没有等待的节点,则直接通过CAS的方式进行判断,然后就是把当前线 程设置为活跃线程

如果有等待的节点,就会跳过CAS的判断,紧接着会去判断当前线程和持有锁的线程是否是同一个线程,如果 是同一个线程,还是进行计数+1,满足可重入性

不是就返回false,此时tryAcquire方法返回false

此时, 我们再把视角拉回到acquire 方法

返回false之后,则会执行addWaiter方法和acquireQueued方法

这段代码首先会创建一个和当前线程绑定的Node节点,Node为双向链表。此时等待对内中的tail指针为空,直 接调用enq(node)方法将当前线程加入等待队列尾部:

第一遍循环时tail指针为空,进入if逻辑,使用CAS操作设置head指针,将head指向一个新创建的Node节点。

此时AQS中数据:

执行完成之后,head、tail、t都指向第一个Node元素。

接着执行第二遍循环,进入else逻辑,此时已经有了head节点,这里要操作的就是将线程二对应的Node节点 挂到head节点后面。

此时队列中就有了两个Node节点:

addWaiter()方法执行完后,会返回当前线程创建的节点信息。继续往后执
行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)逻辑,此时传入的参数为线程二对应的Node节点信息

acquireQueued()这个方法会先判断当前传入的Node对应的前置节点是否为head,如果是则尝试加锁。 加锁成功过则将当前节点设置为head节点,然后空置之前的head节点,方便后续被垃圾回收掉。

如果加锁失败或者Node的前置节点不是head节点,就会通过shouldParkAGerFailedAcquire方法 将head节点的
waitStatus变为了SIGNAL=-1,最后执行parkAndChecknIterrupt方法,调用LockSupport.park()挂起当前线程。

我们不能发现的一点, 就是A Q S 的设计内部, 包括ReentrantLock的设计内部。很多地方都会尝试用C A S 的方式去加锁, 就是因为在高速的运转下, 可能在几行代码的时间一个线程就已经用完锁了, 这样可以最高效率的来利用资源

parkAndCheckInterrupt主要用于挂起当前线程,阻塞调用栈,返回当前线程的中断状态。 给大家看个这里的流程图,图片来源于网络,觉得挺不错

从上图可以看出,跳出当前循环的条件是当“前置节点是头结点,且当前线程获取锁成功”。

为了防止因死循环导致CPU资源被浪费,我们会判断前置节点的状态来决定是否要将当前线程挂起,具体挂起 流程用流程图表示如下(shouldParkAGerFailedAcquire流程):

acquireQueued中最后的finally中,如果失败,则执行cancelAcquire

获取当前节点的前驱节点,如果前驱节点的状态是CANCELLED,那就一直往前遍历,找到第一个waitStatus <= 0的节点,将找到的Pred节点和当前Node关联,将当前Node设置为CANCELLED。

但是为什么所有的变化都是对N e x t指针进行了操作, 而没有对Prev指针进行操作呢? 什么情况下会对Prev指针进行操作?

执行cancelAcquire的时候,当前节点的前置节点可能已经从队列中出去了(已经执行过Try代码块中的shouldParkAGerFailedAcquire方法了),如果此时修改Prev指针,有可能会导致Prev指向另一个已经移除队列 的Node,因此这块变化Prev指针不安全。

shouldParkAGerFailedAcquire方法中,会执行下面的代码,其实就是在处理Prev指针。shouldParkAGerFailedAcquire是获取锁失败的情况下才会执行,进入该方法后,说明共享资源已被获取, 当前节点之前的节点都不会出现变化,因此这个时候变更Prev指针比较安全。

do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
解锁

接下来再对解锁的基本流程进行分析。由于ReentrantLock在解锁的时候,并不区分公平锁和非公平锁,所 以我们直接看解锁的源码:

点进来release之后发现实现还是在AQS框架中

在ReentrantLock里面的公平锁和非公平锁的父类Sync定义了可重入锁的释放锁机制。

这个方法先去减少一次可重入次数,然后判断当前线程是否是持有锁的线程,如果不是,则直接抛出异常接着判断c==0,等于0代表当前的资源处于空闲状态,便可以将当前独占资源的线程设置为null,然后更新state

如果不等于0,这一步释放独占锁的操作便会滤过,就是普通的重入锁减少一次重入次数,就像是重入加锁三次,执行这里之后只是变成2次而已,但是还是该线程持有该资源

总结

我们先是在非公平锁和公平锁的角度分别分析了加锁的过程,得知非公平比公平锁只是多了一个抢先加锁的机 会,但是如果抢不到锁还是会执行和公平锁相同的逻辑

中间我们分析了公平锁和非公平锁的优缺点, 这个是面试热点

然后我们还会发现代码中很多地方都会尝试用CAS的方式去抢占锁,我们知道CPU的运行是很快的,这样能够 保证资源释放释放能够在第一时间被等待队列中的线程抢到锁

最后我们又分析了这个释放锁的过程,这个释放锁并没有公平和非公平的区分,只是其中对于重入锁进行了处 理,就是上面最后一张图的==0操作,因为我们上面分析了重入的道理也是对这个state进行累加得来的,所以这里只需要减一,然后判断是否为0即可

0的时候就意味着此时资源处于空闲状态,这个state是volatile的,保证了可见性

这篇只是一个笼统的分析,其实还有很多细节没有分析到位,只能说AQS的设计很精妙,李老牛皮

结束语

感谢大家能够做我最初的读者和传播者,请大家相信,只要你给我一

份爱,我终究会还你们一页情的。

欢迎大家关注我的公众号【左耳君】,探索技术,分享生活

哦对了,后续所有的文章都会更新到这里

https://github.com/DayuMM2021/Java

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/831679.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号