ReentrantLock底层原理分析

【多线程】轻松彻底搞懂ReentrantLock的底层实现原理 上篇博客讲解了Sychronized的底层实现原理,它是基于jdk实现的,现在此篇博客讲解基于AQS 框架实现的另一个锁ReentrantLock。 阅读详情

J.U.C 简介

Java.util.concurrent 是在并发编程中比较常用的工具类,里面包含很多用来在并发场景中使用的组件。比如线程池、阻塞队列、计时器、同步器、并发集合等等。并发包的作者是大名鼎鼎的 Doug Lea。

Lock

Lock 在 J.U.C 中是最核心的组件,锁最重要的特性就是解决并发安全问题。为什么要以 Lock 作为切入点呢?

如果你有看过 J.U.C 包中的所有组件,一定会发现绝大部分的组件都有用到了 Lock。所以通过 Lock 作为切入点使得在后续的学习过程中会更加轻松。

Lock 简介

在 Lock 接口出现之前,Java 中的应用程序对于多线程的并发安全处理只能基于 synchronized 关键字来解决。但是 synchronized 在有些场景中会存在一些短板,也就是它并不适合于所有的并发场景。但是在 Java5 以后,Lock 的出现可以解决 synchronized 在某些场景中的短板,它比 synchronized 更加灵活。

Lock 的实现

Lock 本质上是一个接口,它定义了释放锁和获得锁的抽象方法,定义成接口就意味着它定义了锁的一个标准规范,也同时意味着锁的不同实现。

实现 Lock 接口的类有很多,以下为几个常见的锁实现

  • ReentrantLock:表示重入锁,它是唯一一个实现了 Lock 接口的类。重入锁指的是线程在获得锁之后,再次获取该锁不需要阻塞,而是直接关联一次计数器增加重入次数

  • ReentrantReadWriteLock:重入读写锁,它实现了 ReadWriteLock 接口,在这个类中维护了两个锁,一个是 ReadLock,一个是 WriteLock,他们都分别实现了 Lock 接口。读写锁是一种适合读多写少的场景下解决线程安全问题的工具,基本原则是: 读和读不互斥、读和写互斥、写和写互斥。也就是说涉及到影响数据变化的操作都会存在互斥。

  • StampedLock: stampedLock 是 JDK8 引入的新的锁机制,可以简单认为是读写锁的一个改进版本,读写锁虽然通过分离读和写的功能使得读和读之间可以完全并发,但是读和写是有冲突的,如果大量的读线程存在,可能会引起写线程的饥饿。stampedLock 是一种乐观的读策略,使得乐观锁完全不会阻塞写线程

Lock 的类关系图

Lock 有很多的锁的实现,但是直观的实现是 ReentrantLock 重入锁

常用API

voidlock()// 如果锁可用就获得锁,如果锁不可用就阻塞直到锁释放voidlockInterruptibly()// 和lock()方法相似, 但阻塞的线程可中断,抛出java.lang.InterruptedException 异常booleantryLock()// 非阻塞获取锁;尝试获取锁,如果成功返回 truebooleantryLock(long timeout, TimeUnit timeUnit)//带有超时时间的获取锁方法voidunlock()// 释放锁

ReentrantLock 重入锁

重入锁,表示支持重新进入的锁,也就是说,如果当前线程 t1 通过调用 lock 方法获取了锁之后,再次调用 lock,是不会再阻塞去获取锁的,直接增加重试次数就行了。synchronized 和 ReentrantLock 都是可重入锁。那为什么锁会存在重入的特性?假如在下面这类的场景中,存在多个加锁的方法的相互调用,其实就是一种重入特性的场景。

重入锁的设计目的

比如调用 demo 方法获得了当前的对象锁,然后在这个方法中再去调用demo2,demo2 中的存在同一个实例锁,这个时候当前线程会因为无法获得demo2 的对象锁而阻塞,就会产生死锁。重入锁的设计目的是避免线程的死锁。

publicclassReentrantDemo{
    publicsynchronizedvoiddemo(){
        System.out.println("begin:demo");
        demo2();
    }

    publicvoiddemo2(){
        System.out.println("begin:demo1");
        synchronized (this) {
        }
    }

    publicstaticvoidmain(String[] args){
        ReentrantDemo rd = new ReentrantDemo();
        new Thread(rd::demo).start();
    }
}

ReentrantLock 的使用案例

publicclassAtomicDemo{
    privatestaticint count = 0;
    static Lock lock = new ReentrantLock();

    publicstaticvoidinc(){
        lock.lock();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
        lock.unlock();
    }

    publicstaticvoidmain(String[] args)throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                AtomicDemo.inc();
            }).start();
            ;
        }
        Thread.sleep(3000);
        System.out.println("result:" + count);
    }
}

ReentrantReadWriteLock

我们以前理解的锁,基本都是排他锁,也就是这些锁在同一时刻只允许一个线程进行访问,而读写所在同一时刻可以允许多个线程访问,但是在写线程访问时,所有的读线程和其他写线程都会被阻塞。读写锁维护了一对锁,一个读锁、一个写锁; 一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。

public classLockDemo{
    staticMap<String, Object> cacheMap = new HashMap<>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock read = rwl.readLock();
    static Lock write = rwl.writeLock();

    public static final Object get(String key) {
        System.out.println("开始读取数据");
        read.lock(); //读锁try {
            return cacheMap.get(key);
        } finally {
            read.unlock();
        }
    }

    public static final Object put(String key, Object value) {
        write.lock();
        System.out.println("开始写数据");
        try {
            return cacheMap.put(key, value);
        } finally {
            write.unlock();
        }
    }
}

在这个案例中,通过 hashmap 来模拟了一个内存缓存,然后使用读写所来保证这个内存缓存的线程安全性。当执行读操作的时候,需要获取读锁,在并发访问的时候,读锁不会被阻塞,因为读操作不会影响执行结果。

在执行写操作是,线程必须要获取写锁,当已经有线程持有写锁的情况下,当前线程会被阻塞,只有当写锁释放以后,其他读写操作才能继续执行。使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性。

  • 读锁与读锁可以共享

  • 读锁与写锁不可以共享(排他)

  • 写锁与写锁不可以共享(排他)

ReentrantLock 的实现原理

我们知道锁的基本原理是,基于将多线程并行任务通过某一种机制实现线程的串行执行,从而达到线程安全性的目的。在 synchronized 中,我们分析了偏向锁、轻量级锁、乐观锁。基于乐观锁以及自旋锁来优化了 synchronized 的加锁开销,同时在重量级锁阶段,通过线程的阻塞以及唤醒来达到线程竞争和同步的目的。那么在 ReentrantLock 中,也一定会存在这样的需要去解决的问题。就是在多线程竞争重入锁时,竞争失败的线程是如何实现阻塞以及被唤醒的呢?

AQS 是什么

在 Lock 中,用到了一个同步队列 AQS,全称 AbstractQueuedSynchronizer,它是一个同步工具也是 Lock 用来实现线程同步的核心组件。如果你搞懂了 AQS,那么 J.U.C 中绝大部分的工具都能轻松掌握。

AQS 的两种功能

从使用层面来说,AQS 的功能分为两种:独占和共享 独占锁,每次只能有一个线程持有锁,比如前面给大家演示的 ReentrantLock 就是 以独占方式实现的互斥锁 共享锁,允许多个线程同时获取锁,并发访问共享资源,比如 ReentrantReadWriteLock

AQS 的内部实现

AQS 队列内部维护的是一个 FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。每个 Node 其实是由线程封装,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去;当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。

Node 的组成

释放锁以及添加线程对于队列的变化

当出现锁竞争以及释放锁的时候,AQS 同步队列中的节点会发生变化,首先看一下添加节点的场景。

这里会涉及到两个变化

  1. 新的线程封装成 Node 节点追加到同步队列中,设置 prev 节点以及修改当前节点的前置节点的 next 节点指向自己

  1. 通过 CAS 讲 tail 重新指向新的尾部节点

head 节点表示获取锁成功的节点,当头结点在释放同步状态时,会唤醒后继节点,如果后继节点获得锁成功,会把自己设置为头结点,节点的变化过程如下

这个过程也是涉及到两个变化

  1. 修改 head 节点指向下一个获得锁的节点

  1. 新的获得锁的节点,将 prev 的指针指向 null

设置 head 节点不需要用 CAS,原因是设置 head 节点是由获得锁的线程来完成的,而同步锁只能由一个线程获得,所以不需要 CAS 保证,只需要把 head 节点设置为原首节点的后继节点,并且断开原 head 节点的 next 引用即可

ReentrantLock 的源码分析

以 ReentrantLock 作为切入点,来看看在这个场景中是如何使用 AQS 来实现线程的同步的

ReentrantLock 的时序图

调用 ReentrantLock 中的 lock() 方法,源码的调用过程我使用了时序图来展现。

ReentrantLock.lock() 这个是 reentrantLock 获取锁的入口

publicvoidlock(){
 sync.lock();
}

sync 实际上是一个抽象的静态内部类,它继承了 AQS 来实现重入锁的逻辑,我们前面说过 AQS 是一个同步队列,它能够实现线程的阻塞以及唤醒,但它并不具备业务功能,所以在不同的同步场景中,会继承 AQS 来实现对应场景的功能,Sync 有两个具体的实现类,分别是:

  • NofairSync:表示可以存在抢占锁的功能,也就是说不管当前队列上是否存在其他线程等待,新线程都有机会抢占锁

  • FailSync: 表示所有线程严格按照 FIFO 来获取锁

NofairSync.lock

以非公平锁为例,来看看 lock 中的实现

  1. 非公平锁和公平锁最大的区别在于,在非公平锁中我抢占锁的逻辑是,不管有没有线程排队,我先上来 cas 去抢占一下

  1. CAS 成功,就表示成功获得了锁

  1. CAS 失败,调用 acquire(1) 走锁竞争逻辑

finalvoidlock(){
 if (compareAndSetState(0, 1))
   setExclusiveOwnerThread(Thread.currentThread());
 else
  acquire(1);
}

CAS 的实现原理

protectedfinalbooleancompareAndSetState(int expect, int update){
 // See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

通过 cas 乐观锁的方式来做比较并替换,这段代码的意思是,如果当前内存中的 state 的值和预期值 expect 相等,则替换为 update。更新成功返回 true,否则返回 false。

这个操作是原子的,不会出现线程安全问题,这里面涉及到Unsafe这个类的操作,以及涉及到 state 这个属性的意义。 state 是 AQS 中的一个属性,它在不同的实现中所表达的含义不一样,对于重入锁的实现来说,表示一个同步状态。它有两个含义的表示

  1. 当 state=0 时,表示无锁状态

  1. 当 state>0 时,表示已经有线程获得了锁,也就是 state=1,但是因为ReentrantLock 允许重入,所以同一个线程多次获得同步锁的时候,state 会递增,比如重入 5 次,那么 state=5。而在释放锁的时候,同样需要释放 5 次直到 state=0其他线程才有资格获得锁

一、适配显示服务 基于rk3568适配显示服务,调动gpu; 阅读详情

相关推荐

CVPR 2024 LaTeX 模板实战:从匿名提交到图表排版的避坑指南

本文详细解析CVPR 2024 LaTeX模板的使用技巧,涵盖匿名提交设置、pdfLaTeX编译器选择、图表排版优化等关键环节。通过实战案例演示智能引用、公式编号、三线表制作等专业技巧,帮助研究者高效完成论文格式规范,规避常见排版错误。特别针对双栏格式下的图片定位、跨栏表格等复杂场景提供解决方案。

weixin_42561040的博客 453

Java同步器之ReentrantLock锁的底层实现原理

Java里的锁有两大类,一类是synchronized锁,一类是concurrent包里的锁(JUC锁)。其中synchronized锁是JAVA语言层面提供的能力,在此不展开,本文主要讨论JUC里的ReentrantLock锁。ReentrantLock的lock()、unlock()等API其实依赖于内部的Synchronizer(注意,不是synchronized)来实现。Synchronizer又分为FairSync和NonfairSync,顾名思义是指公平和非公平。当调用ReentrantLock

l688899886的博客 510

磁感应强度B、磁场强度H、矢量磁位A的study

学完电磁场很久了但是对电磁场中磁感应强度B⃗\vec{B}B、磁场强度HHH、矢量磁位A⃗\vec{A}A,这几个变量所表征的含义一直很模糊,所以就总结了一下

sinat_42386289的博客 1万+

Java锁--Lock实现原理(底层实现)

关于java lock的底层实现原理,讲的有点深,转载学习!Lock完全用Java写成,在java这个层面是无关JVM实现的。在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock、ReadWriteLock(实现类ReentrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQue

技术与生活齐头并进 3万+

Tartancalib标定OAK-FFC_4p(上)

1.使用Tartancalib标定双目或多目相机前,先对单个相机标定获得内参,再获得外参。

qq_51731421的博客 1277

视图库对接系列(GA-T 1400)四、视图库对接系列(本级)注册

在之前的步骤中,我们已经把项目大体的架构已经写出来了。那我们就来实现注册接口。GA-T 1400中的步骤如下:这里的话,我们实现的简单点, 我们不进去鉴权,也就是设备或平台找我们注册的话,我们直接返回成功,不进行验证。如果需要鉴权的话可看我的另外一篇文档。

qq_37716298的博客 1044

轻松学习java可重入锁(ReentrantLock)的实现原理

前言相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么实现的。如果真是这样,而且你有兴趣了解,今天我将带领你轻松的学习下java中非常重要,也非常基础的可重入锁-ReentrantLock的实现机制。

yanyan19880509的专栏 8万+

【100%通过率】华为OD机试真题 C++ 实现【水库蓄水问题】【2022.11 Q4 新题】

给定一个长度为 n 的整数数组height。数组的元素表示山的高度,选择两个元素作为水库的边界,求蓄水量的最大值并输出蓄水量最大时的边界下标(蓄水量相同时输出下标较近的)。

MISAYAONE的博客 1万+

(五)可重入锁ReentrantLock的底层原理实现?

一、先整体了解一下重入锁ReentrantLock的大体思路? 先通过一张图,了解一下ReentrantLock的关系网(就好比看一个人NB不NB得先看他周围的人和家里的亲戚够不够NB,现在就开始看看ReentrantLock这个“家伙”有多牛逼!),先上图 首先ReentrantLock继承了他爸爸的AbstractQueuedSynchronizer的财产,这个人物有什么来历请看上一...

王伟的博客 1万+

VTK API 详解(134):vtkOBJWriter - 写入 OBJ 模型

是 VTK 中用于将数据写入 Wavefront OBJ 文件格式的类。OBJ 格式是广泛支持的 3D 网格模型格式,支持顶点、面和基本几何信息的保存,便于模型在多种三维软件间交换。

yumeiguo的博客 140

基于 Google Colab 的 mask rcnn 训练自己的数据集(以实例分割为例)

详细步骤1 数据集的制作1.1 数据标注1.2 生成数据集2 环境的搭建2.1 搭建流程2.2 注意事项(报错)3 开始训练4 可视化4.1 修改webcam.py文件4.2 进行可视化5 参考文章 1 数据集的制作 1.1 数据标注 使用 labelme=4.5.6 版本进行标注,标注文件指定到保存图片的路径下,也就是json文件和jpg文件放在同一个文件夹里 1.2 生成数据集 先手动划分训练集和测试集(包括图像和标注文件),然后分别整合它们的标注信息,参考代码:1_ships_to_coco.py

G果的博客 8002

Java并发编程之ReentrantLock详解

---

Stay Hungry, Stay Foolish 2万+

【零基础入门unity游戏开发——unity通用篇12】导入导出unity资源包、设置编程工具、创建脚本(组件)规则、MonoBehaviour基类介绍、脚本模板(基于unity6开发介绍)

导入导出unity资源包、设置编程工具、创建脚本(组件)规则、MonoBehaviour基类介绍、脚本模板(基于unity6开发介绍)

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 760

7-ReentrantLock底层原理分析

一,Lock Lock 在 J.U.C 中是最核心的组件。 1,Lock实现 Lock 本质上是一个接口,它定义了释放锁和获得锁的抽象方法,定义成接口就意味着它定义了锁的一个标准规范,也同时意味着锁的不同实现。实现 Lock 接口的类有很多,以下为几个常见的锁实现。 ReentrantLock:表示重入锁,它是唯一一个实现了 Lock 接口的类。重入锁指的是线程在获得锁之后,再次获取该锁不需要阻塞,而是直接关联一次计数器增加重入次数。 ReentrantReadWriteLock:重入读写锁,它实现了 Re

weixin_45596022的博客 7417

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

【JUC系列-05】通过源码分析AQS和ReentrantLock的底层原理

深入理解AQS和ReentrantLock的底层实现原理

zhenghuishengq的博客 7090

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

ReentrantReadWriteLock与ReentrantLock底层获取锁原理,关于AQS的差异分析

从源码可以看出,ReentrantLock是继承的Lock接口,而ReentrantReadWriteLock是实现了ReadWriteLock,我们再看下ReadWriteLock的源码:从源码中我们能得出结论,ReentrantLock、ReentrantReadWriteLock类底层都是Lock接口实现的从源码也能看出ReentrantLock、ReentrantReadWriteLock都具有内部静态类Sync实现AQS来实现获取锁等一系列操作,就不一一展示。

weixin_48947071的博客 1245

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

[03][01][04] ReentrantLock的底层原理分析

文章目录J.U.C 简介LockLock 简介Lock 的实现Lock 的类关系图ReentrantLock 重入锁重入锁的设计目的ReentrantLock 的使用案例ReentrantReadWriteLockReentrantLock 的实现原理AQS 是什么AQS 的两种功能AQS 的内部实现Node 的组成释放锁以及添加线程对于队列的变化ReentrantLock 的源码分析NofairSync.lockCAS 的实现原理Unsafe 类stateOffsetcompareAndSwapIntAQ

安全新司机 1124

reentrantlock底层原理_ReentrantLock源码分析(AQS在重入锁中的具体实现)

前言AQS的作用AQS实现原理上锁添加节点释放锁移除节点从ReentrantLock的使用剖析源码实现ReentrantLock的类图上锁源码剖析释放锁源码剖析其他锁及其他组件对AQS的实现后记前言AQS全称为AbstractQueuedSynchronizer,是JUC(java.util.concurrent)中非常核心的组件,在各种锁、信号量、CountDownLatch、Futu...

weixin_39984963的博客 99

《Java并发编程》- AQS及ReentrantLock底层实现原理分析

AQS及ReentrantLock底层实现原理分析、ReentrantLock与synchronized关键字的区别

weixin_38277138的博客 281

(jvm)synchronized关键字的底层实现原理是什么(从JVM指令和对象头角度分析)?与ReentrantLock相比,它的优势和局限性分别体现在哪些场景?

是 Java 内置的同步机制,其底层实现依赖 JVM 指令和对象头(Object Header)的协作,确保多线程对共享资源的互斥访问。

weixin_43521001的博客 803

Lock底层原理—ReentrantLock、AQS、Condition

Lock底层原理—ReentrantLock、AQS、Condition 文章目录Lock底层原理—ReentrantLock、AQS、Condition1. Lock2. ReentrantLock公平锁和非公平锁SyncNonfairSyncNodeFairSync3. AQS—AbstractQueuedSynchronizerMyLock4. ConditionConditionObject.awaitConditionObject.signal5. 同步队列与等待队列参考: 先来看看J.U.C包下

qq_40788718的博客 868
上一篇: 阿里”热捧“Spring全家桶笔记:Spring+Spring Boot+Spring Cloud+Spring MVC
下一篇: 白活了!谷歌架构师10年心血汇成的《24种设计模式》,这才是正解
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值