ReentrantLock源码解析

定义

可重入锁,对于同一个线程可以重复获得此锁。分为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自旋竞争锁。
在这里插入图片描述
制作不易,点赞收藏,有不对的地方,一定要指正,大家一起探讨。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Hibench 】完成 HDP-Spark 性能测试

&#x1f341; 博主 "开着拖拉机回家"带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,Java基础学习,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341; 希望本文能够给您带来一定的…

首发 | FOSS分布式全闪对象存储系统白皮书

一、 产品概述 1. 当前存储的挑战 随着云计算、物联网、5G、大数据、人工智能等新技术的飞速发展&#xff0c;数据呈现爆发式增长&#xff0c;预计到2025年中国数据量将增长到48.6ZB&#xff0c;超过80%为非结构化数据。 同时&#xff0c;数字经济正在成为我国经济发展的新…

Mathematica(42)-计算N个数值的和

比如&#xff0c;我们要用Mathematica求得到下面的式子&#xff1a; 这就需要用到一个函数&#xff1a;Sum 具体地&#xff0c;Sum函数的使用形式如下&#xff1a; 因此&#xff0c;按照公式就可以得到下面的结果&#xff1a; 如果&#xff0c;我们想要将求和号也加进去&#…

广州华锐互动:3D数字孪生开发编辑器助力企业高效开发数字孪生应用

3D数字孪生开发编辑器是一种新兴的技术&#xff0c;它可以帮助企业更好地管理和维护其物联网设备。这些工具可以帮助企业实现对设备的实时监控、故障排除和优化&#xff0c;从而提高生产效率和降低成本。 数字孪生系统是一种将物理世界与数字世界相结合的技术&#xff0c;它可以…

未来公文的智能化进程

随着技术的飞速发展&#xff0c;公文——这个有着悠久历史的官方沟通方式&#xff0c;也正逐步走向智能化的未来。自动化、人工智能、区块链...这些现代科技正重塑我们的公文制度&#xff0c;让其变得更加高效、安全和智慧。 1.语义理解与自动生成 通过深度学习和NLP&#xff…

04_15页表缓存(TLB)和巨型页

前言 linux里面每个物理内存(RAM)页的一般大小都是4kb(32位就是4kb),为了使管理虚拟地址数变少 加快从虚拟地址到物理地址的映射 建议配值并使用HugePage巨型页特性 cpu和mmu和页表缓存(TLB)和cache和ram的关系 CPU看到的都是虚拟地址&#xff0c;需要经过MMU的转化&#xf…

vue3 injection报错 injection“xxx“ not found.

在封装CheckboxGroup组件的的时候&#xff0c;需要通过provide&#xff0c;代码如下&#xff1a; //父组件 <template><div class"envCheckBoxGroup"><slot></slot></div> </template> <script setup> import { provide …

使用SSH隧道将Ubuntu云服务器Jupyter Notebook端口映射到本地

本文主要实现了在Ubuntu云服务器后台运行Jupyter Notebook&#xff0c;并使用SSH隧道将服务器端口映射到本地 1. 生成配置文件 运行以下命令生成Jupyter Notebook的配置文件&#xff1a; jupyter notebook --generate-config这将在用户主目录下生成一个名为.jupyter的文件夹&…

大数据Flink(六十四):Flink运行时架构介绍

文章目录 Flink运行时架构介绍 一、系统架构 二、​​​​​​​​​​​​​​整体构成 三、作业管理器&#xff08;JobManager&#xff09; 四、任务管理器&#xff08;TaskManager&#xff09; Flink运行时架构介绍 我们已经对 Flink 的主要特性和部署提交有了基本的了…

AI绘画 | 一文学会Midjourney绘画,创作自己的AI作品(快速入门+参数介绍)

一、生成第一个AI图片 首先&#xff0c;生成将中文描述词翻译成英文 然后在输入端输入&#xff1a;/imagine prompt:Bravely running boy in Q version, cute head portrait 最后&#xff0c;稍等一会即可输出效果 说明&#xff1a; 下面的U1、U2、U3、U4代表的第一张、第二张…

HCIP第五节------------------------------------------ospf

一、OSPF基础 1、动态路由分类 2、距离矢量协议 运行距离矢量路由协议的路由器周期性地泛洪自己的路由表。通过路由的交互&#xff0c;每台路由器都从相邻的路由器学习到路由&#xff0c;并且加载进自己的路由表中&#xff0c;然后再通告给其他相邻路由器。 对于网络中的所有…

模型预测笔记(一):数据清洗分析及可视化、模型搭建、模型训练和预测代码一体化和对应结果展示(可作为baseline)

模型预测 一、导入关键包二、如何载入、分析和保存文件三、修改缺失值3.1 众数3.2 平均值3.3 中位数3.4 0填充 四、修改异常值4.1 删除4.2 替换 五、数据绘图分析5.1 饼状图5.1.1 绘制某一特征的数值情况&#xff08;二分类&#xff09; 5.2 柱状图5.2.1 单特征与目标特征之间的…

《Java极简设计模式》第03章:工厂方法模式(FactoryMethod)

作者&#xff1a;冰河 星球&#xff1a;http://m6z.cn/6aeFbs 博客&#xff1a;https://binghe.gitcode.host 文章汇总&#xff1a;https://binghe.gitcode.host/md/all/all.html 源码地址&#xff1a;https://github.com/binghe001/java-simple-design-patterns/tree/master/j…

【路由协议】使用按需路由协议和数据包注入的即时网络模拟传递率(PDR)、总消耗能量和节点消耗能量以及延迟研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

opencv直方图与模板匹配

import cv2 #opencv读取的格式是BGR import numpy as np import matplotlib.pyplot as plt#Matplotlib是RGB %matplotlib inline def cv_show(img,name):cv2.imshow(name,img)cv2.waitKey()cv2.destroyAllWindows() 直方图 cv2.calcHist(images,channels,mask,histSize,ran…

『C语言』数据在内存中的存储规则

前言 小羊近期已经将C语言初阶学习内容与铁汁们分享完成&#xff0c;接下来小羊会继续追更C语言进阶相关知识&#xff0c;小伙伴们坐好板凳&#xff0c;拿起笔开始上课啦~ 一、数据类型的介绍 我们目前已经学了基本的内置类型&#xff1a; char //字符数据类型 short …

如何使用Redis实现附近商家查询

导读 在日常生活中&#xff0c;我们经常能看见查询附近商家的功能。 常见的场景有&#xff0c;比如你在点外卖的时候&#xff0c;就可能需要按照距离查询附近几百米或者几公里的商家。 本文将介绍如何使用Redis实现按照距离查询附近商户的功能&#xff0c;并以SpringBoot项目…

面试之快速学习STL- vector

1. vector底层实现机制刨析&#xff1a; 简述&#xff1a;使用三个迭代器表示的&#xff1a; &#xfffc; 这也就解释了&#xff0c;为什么 vector 容器在进行扩容后&#xff0c;与其相关的指针、引用以及迭代器可能会失效的原因。 insert 整体向后移 erase 整体向前移…

科技云报道:算力之战,英伟达再度释放AI“炸弹”

科技云报道原创。 近日&#xff0c;在计算机图形学顶会SIGGRAPH 2023现场&#xff0c;英伟达再度释放深夜“炸弹”&#xff0c;大模型专用芯片迎来升级版本。 英伟达在会上发布了新一代GH200 Grace Hopper平台&#xff0c;该平台依托于搭载全球首款搭载HBM3e处理器的新型Grac…

优于立方复杂度的 Rust 中矩阵乘法

优于立方复杂度的 Rust 中矩阵乘法 迈克克维特 跟随 发表于 更好的编程 6 分钟阅读 7月 <> 143 中途&#xff1a;三次矩阵乘法 一、说明 几年前&#xff0c;我在 C 年编写了 Strassen 矩阵乘法算法的实现&#xff0c;最近在 Rust 中重新实现了它&#xff0c;因为我继续…