ReentrantLock 底层原理

news/2024/5/20 7:19:05 标签: java, juc, 并发编程, aqs,

目录

一、ReentrantLock入门

二、AQS原理

1、AQS介绍

2、自定义

三、ReentrantLock实现原理

1、非公平的实现

流程

释放流程

2、可重入原理

3、可打断原理

4、公平原理

5、条件变量原理

await流程

signal流程


一、ReentrantLock入门

相对于synchronized它具备如下特点

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

与synchronized一样,都支持可重入

基本语法

java">//获取
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对象,也就是条件变量的创建

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

java">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.niftyadmin.cn/n/421718.html

相关文章

《低代码指南》——维格云低代码新建应用详细介绍

目录 1. 简介 1.1 功能简介 1.2 应用场景 1.3 创建入口 2. 创建方式 2.1 创建空白应用 2.2 从推荐场景搜索 2.3 热门模板安装 2.4 模板中心创建 2.5 定制应用 1. 简介 1.1 功能简介 一个应用是由若干张 表单 和 仪表盘 组成的业务管理系统。 就像一个工厂,由不同…

存储笔记1 导论

Why Information Storag Information Explosion on-command, on-demand how to manage info? 数据种类 (貌似比较重要) 结构化数据半结构化数据非结构化数据 处理周期 数据处理是人or机器对数据进行重组或重新排序,增加特定价值 输…

VMware ESXi 7.0 Update 3m - 领先的裸机 Hypervisor (All OEM Customized Installer CDs)

VMware ESXi 7.0 Update 3m - 领先的裸机 Hypervisor (All OEM Customized Installer CDs) ESXi 7.0 U3m Standard (标准版) ESXi 7.0 U3m Dell (戴尔) 定制版 OEM Custom Installer CD ESXi 7.0 U3m HPE (慧与) 定制版 OEM Custom Installer CD ESXi 7.0 U3m Lenovo (联想) 定…

【头歌-Python】Python第九章作业(初级)第3关

第3关:XRD谱图绘制A 任务描述 附件数据为两列,第一列为 X 值,第二列为 Y 值,中间用制表符\t分隔。 请根据附件中的数据绘制如输出示例所示的 XRD 谱图。 提交程序代码。 评分标准 绘制如输出示例所示的 XRD 曲线图设置XRD曲线…

大数据周会-本周学习内容总结017

开会时间:2023.06.1 15:00 线下会议 目录 01【调研-计算(实时、离线)】 1.1【流程图】 1.2【架构图】 1.3【使用场景】 1.4【技术架构】 02【fhzn项目】 03【专利】 01【调研-计算(实时、离线)】 1.1【流程图】…

TClientDataSet 模拟 EXCEL表

日常处理数据时,经常需要,从EXCEL表格中,批量导入数据,通过 XLSReadWriteII编程,会很快导入。 但是,客户提供的EXCEL表的字段,数据格式,字段的排序,有很大的区别。因此&a…

Java ~ Reference ~ Cleaner【源码】

前言 文章 相关系列:《Java ~ Reference【目录】》(持续更新)相关系列:《Java ~ Reference ~ Cleaner【源码】》(学习过程/多有漏误/仅作参考/不再更新)相关系列:《Java ~ Reference ~ Cleaner…

漂亮国因一颗气球而疯狂给质量团队带来的启示

最近漂亮国因为我国的一颗漂洋过海的淘气的民用气球而疯狂。这颗气球成功躲过了号称全球最先进的防空系统,跨越大半个漂亮国,直到被一居民拍照无意间发现,漂亮国才反应过来。多次派战斗机拦截无果,在气球降到15km后,F2…