首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

不能再被问住了!ReentrantLock 源码、画图一起看一看!

在阅读完 JUC 包下的 AQS 源码之后,其中有很多疑问,最大的疑问就是 state 究竟是什么含义?并且 AQS 主要定义了队列的出入,但是获取资源、释放资源都是交给子类实现的,那子类是怎么实现的呢?下面开始了解 ReentrantLock。

介绍

一个可重入的互斥锁与隐式监视器锁 synchronized 具有相同的基本行为和语义,但功能更强大。

具有以下特征:

1. 互斥性:同时只有一个线程可以获取到该锁,此时其他线程请求获取锁,会被阻塞,然后被放到该锁内部维护的一个 AQS 阻塞队列中。

2. 可重入性:维护state变量,初始为 0,当一个线程获取到锁时,state 使用 cas 更新为 1,本线程再次申请获取锁,会对 state 进行 CAS 递增,重复获取次数即 state,最多为 2147483647 。试图超出此限制会从锁定方法抛出 Error。

3. 公平/非公平性:在初始化时,可以通过构造器传参,指定是否为公平锁,还是非公平锁。当设置为 true 时,为公平锁,线程争用锁时,会倾向于等待时间最长的线程。

基本使用

问题疑问

1. 在 AQS 中介绍 state 时,说 state 含义由子类进行定义,那在 ReentrantLock 中 state 代表什么?

2. ReentrantLock 和 AQS 有什么关系?

3. 线程是如何获取到锁的?

4. 锁的可重入性是如何实现的?

5. 当前线程获取锁失败,被阻塞的后续操作是什么?

6. 公平锁和非公平锁是如何体现的?

7. 锁是如何释放的?

将通过源码及画图的方式,围绕上面几个问题,展开阅读和分析。

源码分析

基本结构

基本结构如图所示,ReentrantLock 类实现了接口 Lock,在接口 Lock 中定义了使用锁时的方法,方法及含义如下:

而 ReentrantLock 也只是实现了 Lock 接口,并实现了这些方法,那 ReentrantLock 和 AQS 到底有什么关系呢?这就需要看内部具体如何实现的了。

通过上面类图可以看出,在 ReentrantLock 中含有两个内部类,分别是NonfairSync、FairSync而它俩又实现了 抽象类 Sync,抽象类 Sync 继承了 AbstractQueuedSynchronizer 即 AQS。具体代码如下:

通过上面代码可以看出:

1. 锁的基本控制是由 NonfairSync 和 FairSync 进行控制的,而它俩的父类 Sync 继承了 AQS (AbstractQueuedSynchronizer),这也就是说明 ReentrantLock 的实现和 AQS 是有关的。

2. NonfairSync 代表非公平锁实现逻辑,FairSync 代表公平锁实现逻辑。

3. 构造器传参可以看出,初始化时,默认为 NonfairSync 非公平锁。也可以指定声明为公平锁或非公平锁,传参 true 为 公平锁,false 为非公平锁。

具体 ReentrantLock 和 AQS 的关系是怎样的,就需要通过加锁的过程来分析了。

lock

如图所示,默认声明非公平锁,lock 方法内部调用sync.lock()此时应该是使用的非公平锁内部的 lock 加锁操作。

1. 首先会 使用 CAS 更新 state 的值, 此时就会发现, state 在这里代表的锁的状态。 0 未加锁,1 加锁。

2. 设置失败,会调用 AQS 的 acquire(1); 方法。

再看下 AQS 的 acquire 代码:

在之前分析 AQS 源码时,已经介绍 tryAcquire 是尝试获取 state 的值,AQS 中并不提供可用的方法,此处是由子类实现的。所以这块代码还是在 NonfairSync 类中自己实现的业务逻辑。

1. 当前线程加锁,直接使用 CAS 方式对 state 从 0 更新为 1,更新成功,则获得锁,更新失败,则获取失败。

2. 更新失败后会调用 AQS 的 acquire(1) 方法, 此处传参为 1。

3. tryAcquire 再次尝试获取锁。

4. state 是 0,尝试获取。获取成功返回 true;

5. state 不是 0,判断是否为当前线程持有,是当前线程持有则对 state 进行累加。

6. tryAcquire 获取锁失败,则走 AQS 的 acquireQueued 逻辑,创建节点,并加入到等待队列中。

流程画图如下:

-初始为单个线程

-此时其他线程来请求获取锁

-加锁流程图

公平锁是如何体现的

拉出来代码比较一下:

可以看出在公平锁(FairSync)中多了一个判断条件

!hasQueuedPredecessors()

hasQueuedPredecessors 方法在 AQS 中,如果有当前线程前面的线程排队返回true,如果当前线程是在队列的头部或队列为空,返回false。

代码如下:

如果当前加锁时已经有节点在排队,那就去节点尾部排队,否则才会去抢占锁。

到这里基本上已经知道公平锁和非公平锁的区别了:

非公平锁:不管有没有节点在排队,都会试图去获取锁,如果获取失败,进入 acquire 方法,还是会试图获取一次,之后才会进入队列中。

公平锁:已经有节点在排队,那就自己去节点后面排队。

tryLock

直接调用的 Sync 中的 nonfairTryAcquire, 尝试获取锁,获取失败,就返回 false,获取到锁或者是当前线程持有锁则对 state 累加后都返回 true。

unlock

发现 unlock 直接调用的 AQS 的 release 方法,进行释放资源。

这块在 AQS 中有介绍,也说明 tryRelease 由子类进行实现,现在在 ReentrantLock 重点关注 tryRelease 的实现。

1.获取当前的 state 进行 -1 操作;

2. 判断了下当前线程是否为持有线程;

3. 如果释放完之后 state 为 0 ,则设置持有线程为 null;

4. 更新并返回 state 的值。

总结

通过上面的源码及画图,基本上对开始的问题已经有了答案:

Q:在 AQS 中介绍 state 时,说 state 含义由子类进行定义,那在 ReentrantLock 中 state 代表什么?

A:在 ReentrantLock 中 state 代表加锁状态,0 没有线程获得锁,大于等于 1 已经有线程获得锁,大于 1 说明该获得锁的线程多次重入。

Q:ReentrantLock 和 AQS 有什么关系?

A:ReentrantLock 内部基于 AQS 实现,无论是锁状态,还是进入等待队列,锁释放等都是基于 AQS 实现。ReentrantLock 的公平锁和非公平锁都是 NonfairSync、FairSync 来实现的,而他们的父类 Sync 继承了 AQS。

Q:线程是如何获取到锁的?

A:线程通过修改 state 字段的状态来获取到锁。

Q:锁的可重入性是如何实现的

A:当前线程发现 state 不是 0 ,则说明有锁已经被获取了,此时会判断当前获取到锁的线程是不是自己,如果是,则对 state 进行累加。

Q:当前线程获取锁失败,被阻塞的后续操作是什么?

A:获取失败,会放到 AQS 等待队列中,在队列中不断循环,监视前一个节点是否为 head ,是的话,会重新尝试获取锁。

Q:公平锁和非公平锁是如何体现的?

A:公平锁主要体现在如果当前队列中已经有排队的线程了,则自己直接排在后面。非公平锁是不管当前队列都没有线程排队,都会直接尝试修改 state 获取锁。

Q:锁是如何释放的?

A:锁释放资源,即将 state 进行 -1 操作,如果 -1 后 state 为 0,则释放节点,后续节点尝试获取锁。此处可以看 AQS 相关逻辑。

公众号:liuzhihangs,记录工作学习中的技术、开发及源码笔记;时不时分享一些生活中的见闻感悟。欢迎大佬来指导!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200726A07FY100?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
http://www.vxiaotou.com