ReentrantLock 底层原理

news2024/11/17 21:30:03

目录

一、ReentrantLock入门

二、AQS原理

1、AQS介绍

2、自定义锁

三、ReentrantLock实现原理

1、非公平锁的实现

加锁流程

释放锁流程

2、可重入原理

3、可打断原理

4、公平锁原理

5、条件变量原理

await流程

signal流程


一、ReentrantLock入门

相对于synchronized它具备如下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量

与synchronized一样,都支持可重入

基本语法

//获取锁
reetrantLock.lock();
try{
    //临界区
}finally{
    //释放锁
    reentrantLock.unlock();
}

可重入

可重入指一个线程如果首次获取这把锁,他就是这把锁的拥有者,有权再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

可打断

如果用的lock.lockInterruptibly()这个方法上锁的话,别的线程是可以通过interrupt方法打断的。比如:我的a线程正在尝试获取锁,但这个lock已经被b线程拿了,b可以如果执行Interrupt就可以把a线程正在尝试的打断直接获取失败不等待。就是一种防止无限制等待的机制,避免死等,减少死锁的产生

锁超时

有个lock.tryLock()方法,返回boolean,获取到就返回true,获取不到锁就返回false,这个方法的可以传入两个参数超时时间,第一个参数数字代表时间,第二个是单位。代表他去tryLock()尝试获取锁的时候最多等待的实践,如果是1和秒就是最多尝试等待一秒,还没拿到就返回false。也是一直超时机制,防止死锁。

公平锁

syn就是非公平的,重量级的monitor的堵塞队列就是非公平的。

ReentrantLock默认是不公平,但是我们可以通过构造方法改成公平的,传的是boolean值

条件变量

syn不满足条件的线程都在一个休息室

而ReentranLock支持多休息室,唤醒的时候也是按照休息室来唤醒

使用流程:

  • await进行等待(前需要获得锁)
  • await执行后,会释放锁,进入conditionObject等待
  • await的线程被唤醒(或打断或超时)去重新竞争lock锁
  • 竞争lock锁成功后,从await后继续执行

在new完ReentranLock之后可以用newCondition()方法创建休息室,然后就可以用new出来的condition调用await方法进入休息室等待,唤醒的话是signal()方法

二、AQS原理

1、AQS介绍

Abstract Queued Synchronizer,抽象队列同步器,是阻塞式锁和相关的同步器工具框架,主要是继承他,然后重用他的功能

特点:

  • state属性用来表示资源的状态(分独占模式和共享模型)子类需要定义如何维护这个状态,控制如何获取锁和释放锁
    • getState 获取状态
    • setState 设置状态
    • compareAndSetState -cas机制设置state状态(cas防止多个线程修改state时线程安全)
    • 独占模式是只有一个线程能够访问资源,共享模式允许多个线程访问资源
  • 提供了基于FIFO的等待队列,类似于Monitor的EntryList
  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似与monitor的waitSet

获取锁:tryAcquire(arg)返回boolean,aqs里面暂停和恢复用park和unpark

释放锁:tryRelease(arg)返回boolean

2、自定义锁

自定义的锁的方法基本上都是通过AQS来进行实现的

  • tryAcquire给state进行修改状态,这次使用的是独享锁,给AQS对象锁设置owner
  • 然后就是tryRelease,主要就是设置state为0,解锁,释放owner
  • isHeldExcusively:判断是不是有对象在占用锁
  • newCondition实际上还是AQS里面的ConditionObject对象,也就是条件变量的创建

最后就是实现锁的方法,基本上都是间接调用同步器的方法来执行

class MyLock implements Lock {

    //先实现同步器,实现AQS抽象类,他已经帮我们写了大多数方法,我们只需要自定义4个方法
    class MySync extends AbstractQueuedSynchronizer{

        @Override
        protected boolean tryAcquire(int arg) {
             //尝试加锁
             if(compareAndSetState(0,1)){
                 //这个用的是CAS的无锁机制加锁防止多线程安全问题
                 setExclusiveOwnerThread(Thread.currentThread());
                 return true;
             }
             return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            //尝试释放锁
            setExclusiveOwnerThread(null);
            //这个state是volatile修饰,防止指令重排
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            //锁是不是被线程持有
            return getState()==1;

        }

        public Condition newCondition(){
            return new ConditionObject();
        }
    }

    private MySync sync=new MySync();

    @Override//加锁
    public void lock() {
        sync.acquire(1);
    }

    @Override//加锁,可以被中断
    public void lockInterruptibly() throws InterruptedException {
           sync.acquireInterruptibly(1);
    }

    @Override//尝试加锁,不成功就放弃
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    //尝试加锁,超时就进入队列
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        long waitTime = unit.toNanos(time);
        return sync.tryAcquireNanos(1,waitTime);
    }

    @Override
    public void unlock() {
        //他这个释放锁,调用的是release方法,这个方法还会唤醒堵塞队列中的一个线程
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
//        sync.newCondition();
        return sync.newCondition();
    }
}

三、ReentrantLock实现原理

他底层也是有个抽象Sync类继承了AQS,他有两个实现NonfairSync非公平锁FairSync公平锁

1、非公平锁的实现

加锁流程

他先用cas的compareAndSetState尝试加锁,当没有竞争时,如果上锁成功就setExclusiveOwnerThread改成当前线程

第一个竞争出现时,他会自己tryAcquire重试一次,如果成功就加锁成功。

如果还是加锁失败就会构建node队列,head指向的是第一个节点称为dummy哑元或哨兵,是占位的,并不关联线程

然后回进入一个死循环汇总不断尝试获取锁,失败后进入park阻塞

如果自己是紧邻head的第二位,那么可以再次tryAcquire尝试获取锁,当仍然state为1,失败

将前驱的node,即head的waitStatus改为-1(表示要堵塞了,head是status-1就可以后面有责任唤醒它),这次返回false

这个时候还会再循环一遍,进入方法因为前驱节点为-1了就回返回true,然后就会调用park方法堵塞当前线程

当有多个节点都park堵塞的时候就会一个个进入,然后每个state上面的小三角都是-1表示前驱节点有责唤醒它

释放锁流程

会tryRelease,如果成功直接设置exclusiveOwnerThread为null,然后state为0

当前队列不为null时,并且head的waitStatus为-1,他就会找到队列中里head最近的node,unpark恢复他的运行,然后就会执行上面加锁的方法。

但是这个是非公平锁,假如这个时候突然来了个不在队列里面的线程4,就会跟刚刚唤醒的线程竞争,如果4先拿到锁设置为exclusiveOwnerThread,那么1将会重新park进入阻塞

2、可重入原理

以非公平锁为例,获取锁会调用nonfairTryAcquire

他会先判断state状态,如果为0说明没有加锁,直接加锁

如果不为0说明上锁了就会判断当前线程是不是和exclusiveOwnerThread里面的一样,如果一样的话就说明是重入了,直接s 

释放的时候就会调用tryRelease方法,调用完就会state--,如果当减到0的时候,就会让锁释放掉,设置exclusiveOwnerThread为null,让state为0

3、可打断原理

不可打断模式

默认情况是不可打断的,线程在没变法立刻获得锁的时候,就会不断循环尝试获取锁,仍然不成功会进入park,进入park是可以被其他线程被唤醒。有线程来打断他,打断标记默认值是false,一旦被打断就会色设置为true,但是他就是改个这个标记,然后又重新进入循环park了。就是唤醒了但是没有拿到锁还是继续阻塞,只是有一个true标记

不可打断模式下,即使被打断,仍会驻留在AQS队列里面,等获得锁后才能继续执行,在获得锁以后才知道其他线程打断我了(打断标记被设置为true)

可打断模式

当调用doAcquireInterruptibly,也是去获取锁,然后park进入队列,这个时候别的线程唤醒它继续执行,直接是抛出InterruptedException异常就直接不用循环等待了,实现了可打断

4、公平锁原理

非公平锁是nonfairTryAcquire去获取锁,获取锁的时候如果为state为0说明没有人拿锁,他会直接去抢锁,没有任何判断

公平锁会多个判断条件,如果堵塞队列还有线程就不会去拿锁

5、条件变量原理

await流程

  • 首先就是把线程放入到对应的await的condition队列(相当于就是休息室)
  • 就是清空锁,防止可重入和获取了别的锁。然后唤醒Sync的队列的线程
  • 然后就是进入condition阻塞

总结:进入condition队列,清空锁并且唤醒线程,最后就是使用park进行阻塞。

signal流程

  • 首先检查线程是不是获取了锁,然后就是获取队列的头结点
  • 接着就是调用doSignal唤醒first加入到Sync的队列(加入到Sync才能够进入到owner竞争锁执行),类似wait之后进入休息室,唤醒后还是要进入队列竞争
  • 接着就是获取下一个节点(也就是真正的条件队列节点),获取如果是null,那么最后的节点设置为空
  • 如果不为空,节点被取出来并且next节点设置为空(已经保存到firstWaiter中)
  • if ( (firstWaiter = first.nextWaiter) == null)
  • lastWaiter = null;
  • first.nextWaiter = null;
  • 首先需要知道这是一个单向链表,而且有指向的队首和指向队尾的节点,可以看下面的addConditionWaiter方法,很明显每次都是直接通过队尾下一个节点指向新节点,然后队尾=新节点。这样子的移动。那么这里的first.nextWaiter就是断开first而已,并没有把firstWaiter设置为null。只是指针没有指向下一个节点。每次相当于就是把firstWaiter队首往后面移动,然后把first节点弄到Sync队列上面去等待。
  • 接着就是到把first节点转移通过方法transferForSignal,并且把节点的状态设置为0
  • Node p = enq(node);最后就是把节点拼接上Sync队列,并且返回前驱节点
  • 修改前驱节点状态-1。结束了signal唤醒

总结:signal完成了条件队列清除(单项链表清除),然后就是把对应的节点全部送去Sync队列。如果失败可能就是队列满了或者是超时了。最后就是取出前驱节点修改状态。

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

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

相关文章

对测试外包的一些粗略看法

什么叫外包,外包最直接理解就是让别人做事;外包其中一项目的就是降低企业经营成本。 从外包的含义和目的来看,就是我们帮人做事、听人指挥,当企业经济不好的时候,我们就成为了降低成本的最佳方案。说这些是让大家比较…

高并发编程:线程池

一、概述 线程池首先有几个接口先了解第一个是Executor,第二个是ExecutorService,在后面才是线程池的一个使用ThreadPoolExecutor。 二、Executor Executor看它的名字也能理解,执行者,所以他有一个方法叫执行,那么执…

JVM原理:JVM垃圾回收算法(通俗易懂)

目录 前言正文垃圾标记算法引用类型强引用软引用弱引用虚引用 引用计数法循环引用问题 根可达性分析法虚拟机栈(栈帧的局部变量表)中的引用方法区中类静态属性引用方法区中常量引用本地方法栈(Native方法)引用 垃圾回收算法标记清…

Java语法进阶及常用技术(八)--线程池

初识线程池 什么是“池” ---- 软件中的“池”,可以理解为计划经济。 我们的资源是有限的,比如只有十个线程,我们创造十个线程的线程池,可能我们的任务非常多,如1000个任务,我们就把1000个任务放到我们十个…

shell脚本学习记录(流程控制)

前言: 在shell脚本中,()、{}、[]都是用来表示命令或者变量的范围或者属性。它们的具体区别如下: ():表示命令在子shell中运行。括号中的命令会在一个子shell中运行,并且该子shell拥符有自己的环境变量和文件描述&#…

【youcans动手学模型】DenseNet 模型-CIFAR10图像分类

欢迎关注『youcans动手学模型』系列 本专栏内容和资源同步到 GitHub/youcans 【youcans动手学模型】DenseNet 模型-CIFAR10图像分类 1. DenseNet 神经网络模型1.1 模型简介1.2 论文介绍1.3 改进方法与后续工作1.4 分析与讨论 2. 在 PyTorch 中定义 DenseNet 模型类2.1 DenseBlo…

性能测试实战——登录接口的性能测试(超详细总结)

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 在实际业务场景中…

python:六个模块,概括全书(上万字最详细版)

拍摄于——无锡南长街 文章目录 模块一:基础知识1、python语言2、常见数字类型3、字符串4、数字类型转换5、标识符命名6、常见关键字7、运算符与表达式(1)算术运算符(2)关系运算符(3)逻辑运算符…

循序渐进,搞懂什么是动态规划

循序渐进,搞懂什么是动态规划 写在前面 温馨提示,本文的篇幅很长,需要花很长的时间阅读。如果要完全理解所有内容,还需要花更多的时间学习。如果打算认真学习动态规划,又不能一次看完,建议您收藏本文以便后…

《深入理解计算机系统》(6)存储器层次结构

1、存储技术 随机访问存储器,分为两类: RAM,同时也是易失性存储器,也分为两类: - SRAM:静态随机访问存储器,速度快,价格高。多用来作为高速缓存存储器。 - DRAM:动态随机…

WinDbg安装入坑1(C#)

由于作者水平有限,如有写得不对的地方,请指正。 使用WinDbg的过程中,坑特别的多,对版本要求比较严格,如: 1 32位应用程序导出的Dump文件要用32位的WinDbg打开,想要没有那么多的问题&#xff…

chatgpt赋能python:Python删除内容:掌握三种删除方式

Python删除内容:掌握三种删除方式 删除变量中的值 删除变量中的值是Python编程中常见的操作。Python提供了del语句用于删除变量中的值: x "Hello World" del x上述代码中,del x语句将删除变量x中的值。如果我们在执行print(x)时…

从C语言到C++_18(stack和queue的常用函数+相关练习)力扣

目录 1. stack 1.1 栈的概念 1.2 stack 的介绍和使用 2. queue 2.1 队列的概念 2.2 queue 的介绍和使用 3. 栈和队列的相关选择题 答案: 4. 栈和队列的相关OJ题 155. 最小栈 - 力扣(LeetCode) 解析代码: 剑指 Offer 3…

python学习-代码调试器

目录 为什么学习调试器Pycharm Debugger示例所用代码布局调试工具栏 Debug Bar程序控制工具栏 pdb查看源代码 l list查看当前函数源代码 ll longlist打印变量 p查看调用栈w where向上移动当前帧 u up向上移动当前帧 d down运行当前行代码,在第一个可以停止的位置停下 s step继续…

Selenium基础篇之八大元素定位方式

文章目录 前言一、如何进行元素定位?1.右击元素-检查2.F12-选择工具点击元素3.借助selenium IDE 二、八大元素定位方式1.ID1.1 方法1.2 举例1.3 代码1.4 截图 2.NAME2.1 方法2.2 举例2.3 代码2.4 截图 3.CLASS_NAME3.1 方法3.2 举例3.3 代码3.4 截图 4.TAG_NAME4.1 …

再也不用担心组件跨层级的数据共享和方法驱动了

文章目录 兄弟组件的传值和方法调用多个独立组件的数据共享和方法调用多个组件内的方法和数据互相驱动:eventBus多个组件的数据共享以及数据修改:vuex 项目中关于组件的使用经常会碰到这种情况:父子组件传和方法调用、兄弟组件的传值和方法调…

Selenium该如何实现微博自动化运营:关注、点赞、评论

目录 前言: Selenium 是什么? 一、核心代码 二、步骤分解 三、自动化运营常用工具 前言: 使用 Selenium 实现微博自动化运营,可以提高效率、减少工作量,下面讲解如何使用 Selenium 实现微博的关注、点赞和评论功…

webSocket 学习

引子 WebSocket 是一种在单个 TCP 连接上进行全双工通信的网络协议。它是 HTML5 中的一种新特性,能够实现 Web 应用程序和服务器之间的实时通信,比如在线聊天、游戏、数据可视化等。 相较于 HTTP 协议的请求-响应模式,使用 WebSocket 可以建…

JVM零基础到高级实战之Java内存区域虚拟机栈

JVM零基础到高级实战之Java内存区域虚拟机栈 JVM零基础到高级实战之Java内存区域虚拟机栈 文章目录 JVM零基础到高级实战之Java内存区域虚拟机栈前言JVM内存模型之虚拟机栈总结 前言 JVM零基础到高级实战之Java内存区域虚拟机栈 JVM内存模型之虚拟机栈 虚拟机栈是什么&#x…

Ansys Lumerical | 光纤布拉格光栅温度传感器的仿真模拟

说明 该示例演示了一种基于光纤布拉格光栅(FBG)的温度传感器,因为光纤折射率会随温度而变化,导致其布拉格波长发生偏移,所以可以被用作温度的测量。(联系我们获取文章附件) 综述 在本示例中要考…