前言

以前只知道ReentrantLock底层基于AQS实现,相对于(旧版本的)synchronized:

  • 更轻量(基于CAS而不是管程),由JDK实现
  • 可以实现公平/非公平
  • 可中断等待
  • 可绑定多个条件,以选择性地通知其他进程解除等待。

那在我们分析ReentrantLock源码之前,首先了解一下ReentrantLock的工作流程:



此图转载自https://blog.csdn.net/qq_27184497

然后看一下上述features是如何实现的。

类定义、构造

  1. public class ReentrantLock implements Lock, java.io.Serializable {
  2. private static final long serialVersionUID = 7373984872572414699L;
  3. /** Synchronizer providing all implementation mechanics */
  4. private final Sync sync;
  5. abstract static class Sync extends AbstractQueuedSynchronizer {

主要实现了Lock接口,规范了一个lock应当具有的基本功能:上锁、解锁、tryLock等。

并且有一个内部抽象类和属性Sync继承自AQS。

构造函数:

  1. public ReentrantLock() {sync = new NonfairSync();}
  2. public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

默认实现非公平锁,可指定实现公平锁。公平/非公平锁的大部分方法都相同,不同的方法在Sync类中定义为抽象方法,由公平/非公平两个子类实现,我们先以非公平锁的实现为例,一会再对比非公平锁。

去看看ReentrantLock主要使用流程的方法们是怎样的。

Lock方法:

  1. public void lock() {sync.lock();}

调用了sync的方法,继续深入:

  1. final void lock() {
  2. if (!initialTryLock())
  3. acquire(1);
  4. }

看一下initialTryLock方法:

  1. final boolean initialTryLock() {
  2. Thread current = Thread.currentThread();
  3. if (compareAndSetState(0, 1)) { // first attempt is unguarded
  4. setExclusiveOwnerThread(current);
  5. return true;
  6. } else if (getExclusiveOwnerThread() == current) {
  7. int c = getState() + 1;
  8. if (c < 0) // overflow
  9. throw new Error("Maximum lock count exceeded");
  10. setState(c);
  11. return true;
  12. } else
  13. return false;
  14. }

首先创建一个线程Thread,然后尝试SetState,

  • 如果成功,将该线程设为独占线程(此方法来自于AQS)
  • 如果失败,那么获取当前独占线程是否为这个current,我理解这里是考虑到可重入场景。(这里state相关函数都是AQS的方法,改天再开一篇博客详解AQS)在这个可重入场景下,变量c的逻辑是为了保证重入多少次,就要unlock多少次。
  • 如果当前独占线程不是这个current,说明上锁失败。

在lock方法中,若该方法return false,则进入acquire(1),

  1. public final void acquire(int arg) {
  2. if (!tryAcquire(arg))
  3. acquire(null, arg, false, false, false, 0L);
  4. }

里面有很多很多参数的这个方法是AQS的方法,公平/非公平、等待时长、中断等最终都是这个方法实现,只不过他们的tryAcquire方法不一样,对于非公平锁而言大概就是自旋不断CAS尝试获取,这是公平/非公平的主要区别,公平锁会进入等待队列,而非公平直接自旋抢占。

lockInterruptibly()方法,可被中断地上锁,和Lock方法基本类似:

  1. final void lockInterruptibly() throws InterruptedException {
  2. if (Thread.interrupted())
  3. throw new InterruptedException();
  4. if (!initialTryLock())
  5. acquireInterruptibly(1);
  6. }
  7. public final void acquireInterruptibly(int arg)
  8. throws InterruptedException {
  9. if (Thread.interrupted() ||
  10. (!tryAcquire(arg) && acquire(null, arg, false, true, false, 0L) < 0))
  11. throw new InterruptedException();
  12. }

下面这个方法来自AQS。如果获取锁之前就中断,那么直接抛出InterruptedException异常。

如果获取锁失败,那么和Lock方法的区别就是自旋前会调用Thread.interrupted()方法,确认当前线程是否被调用了interrupt,如果中断,那么抛出InterruptedException。

基本逻辑就是若两个线程都使用lockInterruptibly获取锁,如果线程A获取到了锁,线程B只能等待,对线程B调用interrupt()方法能够中断线程B的等待过程

tryLock方法:

同样来自于内部的Sync类,tryLock方法有两个,第一个和Lock方法调用的initialTryLock方法基本一样,唯一的区别就是这个方法首先会获取状态,而不是一上来直接CAS尝试获取锁,这里不做分析了,第二个是带等待时长参数的,我们直接进去看:

  1. final boolean tryLockNanos(long nanos) throws InterruptedException {
  2. if (Thread.interrupted())
  3. throw new InterruptedException();
  4. return initialTryLock() || tryAcquireNanos(1, nanos);
  5. }

return的左值很好理解,就是没有经过时长等待就获取到了锁,根据||的短路效应,就没有必要进入右边了。

而右边又是一个AQS方法:

  1. public final boolean tryAcquireNanos(int arg, long nanosTimeout)
  2. throws InterruptedException {
  3. if (!Thread.interrupted()) {
  4. if (tryAcquire(arg))
  5. return true;
  6. if (nanosTimeout <= 0L)
  7. return false;
  8. int stat = acquire(null, arg, false, true, true,
  9. System.nanoTime() + nanosTimeout);
  10. if (stat > 0)
  11. return true;
  12. if (stat == 0)
  13. return false;
  14. }
  15. throw new InterruptedException();
  16. }

大概就是如果在等待时长(nanos参数)内获取到了锁,那么就返回true,如果超时了,那么放弃,返回false。

Unlock方法:

直接调用了AQS的release方法,其中arg是1:

  1. public final boolean release(int arg) {
  2. if (tryRelease(arg)) {
  3. signalNext(head);
  4. return true;
  5. }
  6. return false;
  7. }

signalNext方法是唤醒等待队列中的队头线程,当然了只有公平锁有等待队列。

这个tryRelease方法在AQS中是个钩子方法,也就是交由Sync类实现,我们看一下:

  1. protected final boolean tryRelease(int releases) {
  2. int c = getState() - releases;
  3. if (getExclusiveOwnerThread() != Thread.currentThread())
  4. throw new IllegalMonitorStateException();
  5. boolean free = (c == 0);
  6. if (free)
  7. setExclusiveOwnerThread(null);
  8. setState(c);
  9. return free;
  10. }

首先计算释放后的重入值c,arg是1(所以releases就是1),因此c就是state-1,如果调用释放的线程不是当前工作线程,那么抛出异常。

如果可重入值在释放之后将要变为0,那么说明该锁不被任何线程持有,那么将该锁的独占线程设为null,然后更新可重入值。

Condition

Condition类似于Synchronized中notify,wait等等,只不过更加灵活,一个Lock可以有多个Condition来执行各不相同的条件。

具体用法可以参考这篇文章:

https://blog.csdn.net/wugemao/article/details/83900078

其底层原理不属于ReentrantLock范畴,待日后详解AQS时一并分析。

公平锁的区别:

这里以公平锁的Lock方法为例,其他方法实现公平的方式与之基本一致,公平/非公平锁的Lock方法用得是同一个,但实现不同:

  1. public final void acquire(int arg) {
  2. if (!tryAcquire(arg))
  3. acquire(null, arg, false, false, false, 0L);
  4. }

调用的initialTryLock方法一些区别:

  1. final boolean initialTryLock() {
  2. Thread current = Thread.currentThread();
  3. int c = getState();
  4. if (c == 0) {
  5. if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
  6. setExclusiveOwnerThread(current);
  7. return true;
  8. }
  9. } else if (getExclusiveOwnerThread() == current) {
  10. if (++c < 0) // overflow
  11. throw new Error("Maximum lock count exceeded");
  12. setState(c);
  13. return true;
  14. }
  15. return false;
  16. }

可以看到和非公平锁的initialTryLock方法主要区别就是条件里多加了一个“有排队中的线程”判断,这样只有在当前线程重入,或没有等待线程(等待队列为空)时,才会获取该锁。而公平锁等待队列的实现本就是AQS的核心,因此待日后详解AQS时一并分析。

彩蛋:UID的作用?

源码中有一个属性:

  1. private static final long serialVersionUID = 7373984872572414699L;

这个东西好像也没见到哪个方法里使用了,那它是干什么的?

中文环境中没有查到。。知识盲区了,我理解,大概,类似一种摘要算法生成的code?请大佬解释。。

ReentrantLock源码详解的更多相关文章

  1. 源码详解系列(六) ------ 全面讲解druid的使用和源码

    简介 druid是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,druid还扩展 ...

  2. 源码详解系列(八) ------ 全面讲解HikariCP的使用和源码

    简介 HikariCP 是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,和 dr ...

  3. Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解

    Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解 今天主要理一下StreamingContext的启动过程,其中最为重要的就是Jo ...

  4. spring事务详解(三)源码详解

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

  5. 条件随机场之CRF++源码详解-预测

    这篇文章主要讲解CRF++实现预测的过程,预测的算法以及代码实现相对来说比较简单,所以这篇文章理解起来也会比上一篇条件随机场训练的内容要容易. 预测 上一篇条件随机场训练的源码详解中,有一个地方并没有 ...

  6. [转]Linux内核源码详解--iostat

    Linux内核源码详解——命令篇之iostat 转自:http://www.cnblogs.com/york-hust/p/4846497.html 本文主要分析了Linux的iostat命令的源码, ...

  7. saltstack源码详解一

    目录 初识源码流程 入口 1.grains.items 2.pillar.items 2/3: 是否可以用python脚本实现 总结pillar源码分析: @(python之路)[saltstack源 ...

  8. Shiro 登录认证源码详解

    Shiro 登录认证源码详解 Apache Shiro 是一个强大且灵活的 Java 开源安全框架,拥有登录认证.授权管理.企业级会话管理和加密等功能,相比 Spring Security 来说要更加 ...

  9. udhcp源码详解(五) 之DHCP包--options字段

    中间有很长一段时间没有更新udhcp源码详解的博客,主要是源码里的函数太多,不知道要不要一个一个讲下去,要知道讲DHCP的实现理论的话一篇博文也就可以大致的讲完,但实现的源码却要关心很多的问题,比如说 ...

随机推荐

  1. 用ffmpeg对视频进行处理

    下载安装配置教程:传送门 关键步骤Windows: 官网 合并音频和视频 with open('video/x111.mp4','wb') as f: f.write(data_30080) with ...

  2. 流量录制回放工具jvm-sandbox-repeater入门篇——录制和回放

    在上一篇文章中,把repeater服务部署介绍清楚了,详细可见:流量录制回放工具jvm-sandbox-repeater入门篇--服务部署 今天在基于上篇内容基础上,再来分享下流量录制和回放的相关内容 ...

  3. 定位、z-index、JavaScript变量和数据类型

    溢出属性 # 文本内容超出了标签的最大范围 overflow: hidden; 直接隐藏文本内容 overflow: auto\scroll; 提供滚动条查看 # 溢出实战案例 div { overf ...

  4. MVC 与 Vue

    MVC 与 Vue 本文写于 2020 年 7 月 27 日 首先有个问题:Vue 是 MVC 还是 MVVM 框架? 维基百科告诉我们:MVVM 是 PM 的变种,而 PM 又是 MVC 的变种. ...

  5. 探索ABP基础架构

    为了了解应用程序是如何配置和初始化,本文将探讨ASP.NET Core和ABP框架最基本的构建模块.我们将从 ASP.NET Core 的 Startup类开始了解为什么我们需要模块化系统,以及 AB ...

  6. Hbase数据库安装部署

    Hbase单机版安装 hbase介绍 HBase – Hadoop Database是一个分布式的.面向列的开源数据库,该技术来源于Chang et al所撰写的Google论文"Bigta ...

  7. 重新认识 MSBuild - 1

    前言 很多人一谈到 MSBuild,脑子里就会出现 "XML"."只能用 VS 的属性框图形界面操作"."可定制性和扩展性差" 和 &quo ...

  8. 04 Springboot 格式化LocalDateTime

    Springboot 格式化LocalDateTime 我们知道在springboot中有默认的json解析器,Spring Boot 中默认使用的 Json 解析技术框架是 jackson.我们点开 ...

  9. 【FineBI】增量数据更新语句

    SELECT * FROM t_abike_user WHERE AddUserTime BETWEEN '2016-11-17 10:49:04' AND '2021-07-31 23:59:59'

  10. [学习笔记] pd_ds黑科技

    https://www.cnblogs.com/jiqimin/p/11226809.html 丢个链接,跑路 // Author: wlzhouzhuan #pragma GCC optimize( ...