原文地址: https://blog.csdn.net/lsgqjh/article/details/63685058

ReentrantLock是JUC包中重要的并发工具之一,支持中断和超时、还支持尝试机制获取锁, 并且是一种通过编程控制的可重入锁,尽可能减少死锁问题。本文以公平与非公平锁的加锁释放锁过程成为主线,分析ReentrantLock的重要内容。

目标

  1. 掌握AQS基本原理,理解ReentrantLock的具体实现
  2. 挖掘体会AQS精妙设计

总之,学习AQS过程,要多思考为什么要这样实现,善于带着问题主动探索答案,不断的回过头去看,去思考,我们对并发会有更深的理解。在学习过程中沉淀的“pattern”认知和学习能力能够轻松的复制到我们工作实践和其它优秀代码的学习中。工作后时不时会遇到业界通用方案不匹配自身业务,到造轮子的时候可能才会后悔之前没有多多沉淀吧~。同时感谢我参考学习过的大神资料。

准备知识

ReentrantLock类图:

理想情况下,线程A拿到锁,执行完后释放锁,线程B恰好到来顺手接下这把锁,一切都那么完美的话,也没必要加锁了,问题就在于在A没释放锁时,后续线程也想得到这把锁,所以只好让这些等待的线程进行排队,进而需要一套数据结构来组织这个队伍:

Node结点:作为获取锁失败线程的包装类, 组合了Thread引用, 实现为FIFO双向队列。 下图为Node结点的属性描述

下图为用Node节点构成的双向链表图示:

CLH queues need a dummy header node to get started. But we don’t create them on construction, because it would be wasted effort if there is never contention. Instead, the node is constructed and head and tail pointers are set upon first contention.

链表初始化的头节点其实是一个虚拟节点,英文名称之为dummy header, 因为它不会像后继节点一样真实的存放线程,并且这个节点只会在真正产生竞争排队情况下才会延迟初始化,避免性能浪费,下面看代码的时候,我会再次提到。
AbstractQueuedSynchronizer 类是一个模版类,维护了着一个同步队列(双向链表),提供着同步队列一些操作的公共方法,JUC并发包里基于此类实现了很多常用的并发工具类,如 Semaphore, CountDownLatch等。

   /*** The synchronization state.*/private volatile int state;

AbstractQueuedSynchronizer维护了一个state变量,来表示同步器的状态,state可以称为AQS的灵魂,基于AQS实现的好多JUC工具,都是通过操作state来实现的,state为0表示没有任何线程持有锁;state为1表示某一个线程拿到了一次锁,state为n(n > 1),表示这个线程获取了n次这把锁,用来表达所谓的“可重入锁”的概念。

锁的创建

非公平锁(默认)

final ReentrantLock lock = new ReentrantLock();
final ReentrantLock lock = new ReentrantLock(false);

公平锁

final ReentrantLock lock = new ReentrantLock(true);

非公平锁加锁过程

lock()方法的逻辑: 多个线程调用lock()方法, 如果当前state为0, 说明当前没有线程占有锁, 那么只有一个线程会CAS获得锁, 并设置此线程为独占锁线程。那么其它线程会调用acquire方法来竞争锁(后续会全部加入同步队列中自旋或挂起)。当有其它线程A又进来想要获取锁时, 恰好此前的某一线程恰好释放锁, 那么A会恰好在同步队列中所有等待获取锁的线程之前抢先获取锁。也就是说所有已经在同步队列中的尚未被 取消获取锁 的线程是绝对保证串行获取锁,而其它新来的却可能抢先获取锁。后面代码解释。

    static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}/*** 此为AQS的protected方法,允许子类重写, 在这里被NonfairSync类重写*/protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}

先看acquire方法:
逻辑:tryAcquire方法仍然尝试获取锁(快速获取锁机制),成功返回false,如果没有成功, 那么就将此线程包装成Node加入同步队列尾部。。Node.EXCLUSIVE 为null表示这是独占锁,如果为读写锁,那就是 共享模式(shared)。

    public final void acquire(int arg) {// tryAcquire()方法也是让新来的线程进行第二次插队的机会!!//如果再次获取锁还不成功才会放到队列if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

再看addWaiter 逻辑:

  1. Node包装当前线程
  2. pred 尾指针不为null,即队列不为空, 则快速CAS将自己设为新的tail
  3. 如果队列为空, 则调用enq强制入队
  4. 如果CAS设置失败,说明在其它线程入队节点争抢了tail,则此线程只能调用enq强制入队

注意这里在链表尾部添加节点时,先设置的prev,我们先记住这个事情。

 private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}
 Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}

看Node的构造方法,属性 nextWaiter 值在此被赋予EXCLUSIVE表示独占模式。

下面是enq方法,方法内是一个for(;;),看来退出的条件只能是当前线程入队成功。之前也提到过,只有在产生锁竞争了,才会去初始化链表头节点。如果队列为空,初始化头尾节点,然后后续循环会走到else,else的逻辑和上线CAS入队的逻辑一样,只不过这里套在for循环里,直到入队成功才退出循环。

private Node enq(final Node node) {for (;;) {Node t = tail; if (t == null) { // 判断队列是否为空,空队列应该先初始化头节点if (compareAndSetHead(new Node()))tail = head;//头尾共同指向头结点} else {//CAS添加并允许失败, 走for(;;)node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;//返回新插入节点的前置节点}}}}

入队完成后看下acquireQueued 逻辑:

failed 标记最终acquire是否成功, interrupted标记是否曾被挂起过。注意到for(;

深入剖析ReentrantLock公平锁与非公平锁源码实现相关推荐

  1. 闲聊AQS面试和源码解读---可重入锁、LockSupport、CAS;从ReentrantLock源码来看公平锁与非公平锁、AQS到底是怎么用CLH队列来排队的?

    AQS原理可谓是JUC面试中的重灾区之一,今天我们就来一起看看AQS到底是什么? 这里我先整理了一些JUC面试最常问的问题? 1.Synchronized 相关问题以及可重入锁 ReentrantLo ...

  2. 深入分析ReentrantLock公平锁和非公平锁的区别 (转)

    在ReentrantLock中包含了公平锁和非公平锁两种锁,通过查看源码可以看到这两种锁都是继承自Sync,而Sync又继承自AbstractQueuedSynchronizer,而AbstractQ ...

  3. ReentrantLock与公平锁、非公平锁实现

    前言  最近开始读JDK源码,所有心得准备总结成一个专栏,JDK Analysis系列的第一篇,就从万众瞩目的ReentrantLock开始吧,而谈到ReentrantLock,就不得不说AQS,它是 ...

  4. Java进阶:ReentrantLock实现原理解析(公平锁、非公平锁、可重入锁、自旋锁)

    概述 本篇将介绍公平锁.非公平锁.可重入锁.自旋锁相关理论知识,同时结合相关源码和Demo进行解析,主要是以ReentrantLock作为例子. 公平锁 公平锁定义 公平锁是指线程按照申请所的顺序来获 ...

  5. reentrantlock非公平锁不会随机挂起线程?_【原创】Java并发编程系列16 | 公平锁与非公平锁...

    本文为何适原创并发编程系列第 16 篇,文末有本系列文章汇总. 上一篇提到重入锁 ReentrantLock 支持两种锁,公平锁与非公平锁.那么这篇文章就来介绍一下公平锁与非公平锁. 为什么需要公平锁 ...

  6. 公平锁非公平锁的实际使用_理解ReentrantLock的公平锁和非公平锁

    学习AQS的时候,了解到AQS依赖于内部的FIFO同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个Node对象并将其加入到同步队列,同时会阻塞当 ...

  7. ReentrantLock 公平锁和非公平锁加锁和解锁源码分析(简述)

    - title: ReentrantLock 公平锁和非公平锁加锁和解锁源码分析(简述) - date: 2021/8/16 文章目录 一.ReentrantLock 1. 构造函数 二.Reentr ...

  8. 浅谈ReentrantLock的公平锁和非公平锁的区别

    前言 最近在看java并发编程这本书,已经看了点ReentrantLock的源码,以及之前有面试官问,公平锁和非公平锁有啥区别,我就只是从源码层面说了一下区别,但在性能上也有区别,今天就来说道说道. ...

  9. java中ReentrantLock实现,公平锁和非公平锁,AQS并发队列,

    一般在java中,遇到并发的时候,我们很多时候可能会使用synchronized关键字来实现锁,但是synchronized关键字有一定的缺陷(比如无法实现类似读锁.非公平),而Lock可以实现.在j ...

  10. 6※、线程同步、同步锁、同步代码块的使用、同步锁释放的时机、ReentrantLock可重入锁、公平锁与非公平锁的区别、什么是死锁、线程间的通信(生产者和消费者模式)

    线程锁 1.※线程的同步:(要确保对象锁是一致的) 1.未使用同步锁的抢票 2.使用了同步锁的抢票 3.线程-同步代码块的使用 4.同步方法和代码块的区别 5.同步锁释放的时机 练习:多线程生产手机 ...

最新文章

  1. 使用ABAP正则表达式解析HTML标签
  2. mongodb 系列 ~ mongo 用户验证系列
  3. c语言链表末尾怎么插入数据,在链表中插入数据!求助!!!
  4. UNIX环境高级编程之第5章:标准I/O库
  5. Fortran与C/C++混合编程示例
  6. 关于主机的思维导图_「停课不停学」思维导图—初中数学全部知识点总结,高清可打印...
  7. excel冻结窗口_excel中使用快捷方式锁定单元格,冻结某些单元格、公式或单元格...
  8. mac11.6 安装JD-GUI
  9. jQuery插件实现瀑布流
  10. python中文开发环境_python中文开发环境
  11. spark(scala) shell 里面输入多行代码
  12. Dynamics CRM 向视图列添加自定义图标和提示信息
  13. 定额人工费调整差额的几个解决方案
  14. Linux 系统设置 : hwclock 命令详解
  15. vue 一键复制粘贴文字功能
  16. ECSHOP商城后台无法登陆
  17. 计算机专业的大学生必考证书,大学必考8大证书计算机
  18. @Scheduled(cron = “* * * * * *“) 注解 cron 表达式使用
  19. i春秋2020新春公益赛WEB复现Writeup
  20. 云POS连锁版收银系统免费试用 超市连锁收银软件免费注册

热门文章

  1. 音视频播放器开发——实现变速播放
  2. Java网课基础笔记(4)19-07-16
  3. 机器学习领域权威会议与期刊整理
  4. zoj2100-Seeding
  5. VLAN间如何实现互连?干货奉上!!!
  6. 直播APP搭建常见难点的解决方案
  7. 【一起学Rust | 框架篇 | Frui框架】rust一个对开发者友好的GUI框架——Frui
  8. 如何搭建一个站内搜索引擎(一) 第1章 写在最前
  9. 最热开源实时提升动画画质MPV+Anime4K使用方法
  10. 如何安装OpenCVE