ReentrantLock源码解析

news2025/1/23 17:33:07

定义

可重入锁,对于同一个线程可以重复获得此锁。分为FailLock和NonfairLock。
加锁就是将exclusiveOwnerThread设置为当前线程,且将status加一,解锁就status-1,且exclusiveOwnerThread设置为null。
公平锁:根据先来后到的顺序获得锁,可以避免饥饿现象,所有线程都有同等的机会获取锁。
非公平锁:一进入临界区就开始竞争锁,竞争不到再进入阻塞队列等待,会出现饥饿现象,但是可以减少线程阻塞和唤醒及队列维护的开销,有效提高吞吐量

ReentrantLock和Synchronized的区别

  1. 前者是在Java Api层面进行加锁的,而Synchronized是在JVM层面进行加锁的。
  2. 前者是底层使用cas操作和volatile实现的加锁,而后者是利用操作系统的互斥机制实现的。
  3. 前者需要手动解锁操作,后者自动解锁。
  4. 前者提供了公平锁和非公平锁,而后者只有非公平锁
  5. 前者如果在临界区抛出异常,不会释放锁,需要自己在finally中释放,而后者自动释放。
  6. 前者允许线程在等待锁的过程中响应中断阻塞lockInterruptibly,后者不可以。
  7. 前者可以非阻塞地获取锁,使用tryLock方法,如果没获得锁就返回false,后者不行。
  8. 前者可以通过condition实现分组唤醒阻塞的线程,而后者只能通过notify和notifyall唤醒阻塞的线程。
  9. 前者有很多api,更灵活地实现加锁,也更加复杂了。

在这里插入图片描述

主要方法:

  • lock:加锁
  • lockInterruptibly:等待获取锁的过程中,如果当前线程被中断(即其他线程调用了当前线程的 interrupt 方法),则会立即响应中断,抛出 InterruptedException 异常,并且在异常抛出之前会释放之前已经获得的锁
  • tryLock:非阻塞获取锁,如果获取到了返回true,获取不到返回false。
  • unlock:解锁。

公平锁lock方法

FairSync.lock();

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
    }

Sync继承了AbstractQueuedSynchronized,AQS的acquire方法

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//尝试获取锁,如果没有获取到
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//如果没有获取到锁,则封装成node,追加到阻塞队列中,然后将线程挂起
            selfInterrupt();//当前线程中断
}

FairLock.tryAcquire(arg)

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();//获取AQS同步器状态,volatile修饰,
            //作用:1,保证共享变量在多线程之间的可见性2,防止指令重排序
            if (c == 0) {//无锁状态
                if (!hasQueuedPredecessors() &&//如果阻塞队列中已经没有其他线程了
                    compareAndSetState(0, acquires)) {//cas将c从0更新为acquires
                    setExclusiveOwnerThread(current);//将锁的独占线程设置为当前线程
                    return true;
                }
            }
            //下面这块代码就是可重入锁的重入。
            else if (current == getExclusiveOwnerThread()) {//或者当前线程已经获得了这个锁,增加status
                int nextc = c + acquires;
                if (nextc < 0)//当达到integer最大值,对于有符号二进制而言,再+1,就会变成负数
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

AQS.acquireQueued

final boolean acquireQueued(final Node node, int arg) {//返回true,表示需要中断线程
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {//不间断地获取锁,如果获取到就返回,没获取到就将自己挂起,等到前一个节点执行完,就能将当前节点唤醒
                final Node p = node.predecessor();//上一个节点
                if (p == head && tryAcquire(arg)) {//上一个节点是头节点,重新获取锁,获取到锁返回true
                    setHead(node);//获取到锁之后,将自己设置为头节点
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;//lockInterruptibly使用的
                }
                //没轮到自己,或者轮到自己但是被非公平锁抢了,则
                if (shouldParkAfterFailedAcquire(p, node) &&//获取锁失败是否将线程挂起,可以进入这个方法查看
                    parkAndCheckInterrupt())//进入到这里说明要将线程挂起,线程挂起后,前一个节点的线程释放锁前会将当前线程唤醒,唤醒之后,当前线程继续执行这里的代码,然后重新获取锁。 然后检查是否中断线程(和lockInterruptibly有关)
                    interrupted = true;//和lockInterruptibly有关
            }
        } finally {
            if (failed)
                cancelAcquire(node);//取消当前线程的获取锁操作。
        }
    }

AQS.shouldParkAfterFailedAcquire

       waitStatus的取值
       /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;//表示当前节点线程已取消
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;//表示可以成功唤醒下一个节点
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;//表示线程正在等待条件被唤醒
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;//先不管
        
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;//前一个节点线程的等待状态
        if (ws == Node.SIGNAL)//此标志表示当前节点可以被前一个节点唤醒,所以当前节点可以安心地挂起了,之后这里可以返回true,如果没有进入这里,那么就需要第二次进入此方法。
            return true;
        if (ws > 0) {//表示上一个节点线程取消,那么就一直往上找到可以成功唤醒自己的线程。
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {//等于0或者小于-1,则通过cas将上一个线程的waitstatus改为signal状态。
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

NonfairSync

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))//一来就竞争锁,看是否有刚好释放的锁。
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

非公平锁实现 nonfairTryAcquire

 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {//不用排队,直接竞争,
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;//没有获取到锁,则和公平锁一样,将线程加入阻塞队列。
        }

总结:

1,不管是公平锁还是非公平锁,获得锁的成功的标志是:status加一,然后AQS的独占线程是自己(exclusiveOwnerThread = thread), 如果获取锁的时候发现是自己的锁,那么可以再次获取,举措就是状态加一。
在这里插入图片描述

2,非公平锁一开始就会cas竞争一次锁,然后在获取锁的时候不管等待队列,再cas竞争一次锁。而公平锁获取锁的时候,首先会看等待队列里有没有线程,有的话就加入等待队列,没有就cas竞争锁。
在这里插入图片描述

3,两者如果都没有获取到锁,则会被封装成节点加入等待队列。加入队列操作也是采用cas自旋操作。
在这里插入图片描述

4,加入到等待队列后,看是否自己前一个节点是否头节点,如果是头节点,那么可以尝试获取锁。不是头节点看后一步,。
5,确保当前节点的上一个节点可以成功将自己唤醒,也就是上一个节点的waitstatus为-1。
在这里插入图片描述

6,接下来当前线程就可以挂起了。等前一个节点释放锁将自己唤醒,就可以继续重新cas自旋竞争锁。
在这里插入图片描述
制作不易,点赞收藏,有不对的地方,一定要指正,大家一起探讨。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/894930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C# Linq源码分析之Take (三)

概要 本文在前两篇Take源码分析的基础上&#xff0c;着重分析Range参数中有倒数的情况&#xff0c;即分析TakeRangeFromEndIterator的源码实现。 源码及分析 TakeRangeFromEndIterator方法用于处理Range中的开始和结束索引存在倒数的情况。该方法位于Take.cs文件中。通过yie…

Android4:约束布局

创建项目My Constraint Layout 一般创建项目之后activity_main.xml文件默认就是采用约束布局&#xff0c;如&#xff1a; <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayoutxmlns:android"http:…

FastDFS文件管理知识点+案例分析

一 介绍FastDFS 原理 FastDFS开源分布式文件系统由C语言编写实现, 可以通过专有API访问&#xff0c;目前提供了C、Java和PHP API。 FastDFS是一个开源的轻量级分布式文件系统&#xff0c;它对文件进行管理&#xff0c;功能包括&#xff1a;文件存储、文件同步、文件访问&#x…

scala中json4s 使用详解

预备知识 json4s的数据结构AST (Abstract Syntax Tree)。 sealed abstract class JValue case object JNothing extends JValue // zero for JValue case object JNull extends JValue case class JString(s: String) extends JValue case class JDouble(num: Double) extend…

企业需求如何精准对接科研院校科技成果?

一系列科技成果转化相关政策、法律法规的出台&#xff0c;正在破除高校科技成果转化机制上的桎梏。企业科技创新的需求也越来越大&#xff0c;也充分意识到与高校合作对于推动企业发展的重要性&#xff0c;对接科技成果的热情高涨&#xff0c;合作越来越频繁。但是也存在不成功…

SpringBoot 的 RedisTemplate、Redisson

一、Jedis、Lettuce、Redisson的简介 优先使用Lettuce&#xff0c; 需要分布式锁&#xff0c;分布式集合等分布式的高级特性&#xff0c;添加Redisson结合使用。 对于高并发&#xff0c;1000/s的并发&#xff0c;数据库可能由行锁变成表锁&#xff0c;性能下降会厉害。 1.1、…

linux中ubuntu安装hashcat方法以及使用GPU破解

一、linux安装hashcat git clone https://github.com/hashcat/hashcat.git make make install二、安装驱动 驱动版本安装大全&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive 查看自己显卡选择对应的版本&#xff0c;根据下面命令无脑安装就行了 wget https:/…

誉天华为数通Datacom HCIE认证课程

作为当前网络领域最具含金量的认证之一&#xff0c;华为Datacom HCIE认证不仅是网络工程师们技术能力的证明&#xff0c;也是未来行业趋势的风向标。 尽管经历了几度版本更迭、考题变化&#xff0c;但是誉天一直紧跟技术发展趋势&#xff0c;在专业领域和教学内容上始终与当前的…

生成国密密钥对

在线生成国密密钥对 生成的密钥对要妥善保管&#xff0c;丢失是无法找回的。

奇迹MU服务端IGC架设流程

奇迹MU服务端IGC架设流程 大家好我是艾西&#xff0c;今天跟大家简单聊下奇迹IGC服务端。IGC端是国外的东西全英文大家感兴趣的话也可以自己研究研究&#xff0c;整体的东西还是非常完整。扩展以及端的稳定性还是非常不错的就是有点费脑子........&#xff08;此教程为个人娱乐…

WebStorm运行vue项目

WebStorm运行vue项目&#xff08;vue2&#xff09; 1.安装webstorm 2. 需要安装node.js 环境&#xff0c;可以去官网下载 https://nodejs.org/en 3. 安装完需要查看 按winr 输入cmd进入 输入命令node -v 和npm -v 查看&#xff0c;会出现相应的版本号,代表安装成功 vue官网安…

IC设计中主要的EDA工具有哪些? (内附EDA虚拟机安装资源)

EDA工具的使用涵盖了芯片的功能设计、综合、验证、物理设计等环节&#xff0c;更是被称作“芯片设计的工作母机”。下面就来为大家具体介绍一下常见的EDA工具。&#xff08;需要EDA虚拟机安装资源文末可领取~&#xff09; 什么是EDA&#xff1f; EDA是电子设计自动化&#xf…

对象存储服务-MinIO基本集成

是什么 MinIO 是一个高性能的分布式对象存储服务&#xff0c;适合存储非结构化数据&#xff0c;如图片&#xff0c;音频&#xff0c;视频&#xff0c;日志等。对象文件最大可以达到5TB。 安装启动 mkdir -p /usr/local/minio cd /usr/local/minio# 下载安装包 wget https:/…

NextJs - Error Handling (错误处理)

Error.js 的作用 使用 error.js 文件规则&#xff0c;可以优雅地处理嵌套路由中的意外运行时错误。 在 React Error Boundary 中自动封装路由段及其嵌套子段。 使用文件系统层次结构来调整粒度&#xff0c;为特定网段量身定制错误 UI。 将错误隔离到受影响的网段&#xff0…

红帆OA SQL注入漏洞复现

0x01 产品简介 红帆iOffice.net从最早满足医院行政办公需求&#xff08;传统OA&#xff09;&#xff0c;到目前融合了卫生主管部门的管理规范和众多行业特色应用&#xff0c;是目前唯一定位于解决医院综合业务管理的软件&#xff0c;是最符合医院行业特点的医院综合业务管理平…

怎么修改图片的分辨率?

怎么修改图片的分辨率&#xff1f;很多人还不知道分辨率是什么意思&#xff0c;以为代表了图片的清晰度&#xff0c;然而并不是这样的&#xff0c;其实图片的分辨率就是图片尺寸大小的意思。修改图片的分辨率即改变图片的尺寸&#xff0c;通常以像素为单位表示。分辨率决定了图…

【腾讯云Cloud Studio实战训练营】使用Cloud Studio社区版快速构建React完成点餐H5页面还原

陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f468;‍&#x1f4bb;上一篇…

如何优雅做好项目管理?

导言 项目本身无好坏之分&#xff0c;项目管理有做好与做坏之别。在互联网大厂的体制下&#xff0c;想要做坏一个项目很难&#xff08;可以通过换人、追加资源等方式消除风险&#xff09;&#xff0c;想要做好一个项目不容易&#xff0c;需要团队及PM付出大量心血和精力。在这…

实时业务需求和数据处理要求的满足:流处理的能力

随着数据量和数据种类的不断增加&#xff0c;企业和组织对于实时数据处理和业务响应的需求也越来越迫切。传统的批处理方式无法满足业务对于实时性和即时响应的要求&#xff0c;因此&#xff0c;流处理技术应运而生。通过流处理能力&#xff0c;可以有效地处理实时数据&#xf…

Python爬虫常用:谷歌浏览器驱动——Chromedriver 插件安装教程

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 我们在做爬虫的时候经常要使用谷歌浏览器驱动&#xff0c;今天分享下这个Chromedriver 插件的安装方法。 话不多说&#xff0c;直接开搞&#xff0c;如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 第一步、打开谷…