栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

ReenTrantLock可重入锁(和synchronized及锁升级的区别)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ReenTrantLock可重入锁(和synchronized及锁升级的区别)

目录

1.Synchronized底层原理

2.ReentrantLock

ReenTrantLock实现的原理及使用:

两者之间区别(和synchronized的区别)

synchronized锁升级


一、基本概念和使用

可重入锁: 也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。

在JAVA中ReentrantLock 和synchronized 都是可重入锁;

ReentrantLock 在Java也是一个基础的锁,ReentrantLock 实现Lock接口提供一系列的基础函数,开发人员可以灵活的是应用函数满足各种复杂多变应用场景;

1.Synchronized底层原理

        Synchronized进过编译,会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。

        如果修饰的是方法,则会在方法前标记ACC_Synchronized ,本质都是获取monitor对象。

2.ReentrantLock

由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:

        1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。

        2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
        3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

ReenTrantLock实现的原理及使用:

简单来说,ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。

ReenTrantLock使用:

1.Lock接口:2、ReentrantLock私有public方法3.ReentrantLock中Condition的使用4.公平锁与非公平锁5.编程灵活度和难度。

Java中的ReentrantLock 也是实现了Java中锁的核心接口Lock,在Lock接口定义了标准函数,但是具体实现是在实体类中[类似List和ArrayList、linkedList关系];

2、ReentrantLock私有public方法

ReentrantLock中除了实现Lock中定义的一些标准函数外,同时提供其他的用于管理锁的public方法

3.ReentrantLock中Condition的使用
        ReentrantLock中另一个重要的应用就是Condition,Condition是Lock上的一个条件,可以多次newCondition()获得多个条件,Condition可用于线程间通信,通过Condition能够更加精细的控制多线程的休眠与唤醒,而且在粒度和性能上都优于Object的通信方法(wait、notify 和 notifyAll);

4.公平锁与非公平锁

公平锁: 是指多个线程竞争同一资源时[等待同一个锁时],获取资源的顺序是按照申请锁的先后顺序的;公平锁保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁,有点像早年买火车票一样排队早的人先买到火车票;
基本特点: 线程执行会严格按照顺序执行,等待锁的线程不会饿死,但 整体效率相对比较低;

非公平锁: 是指多个线程竞争同一资源时,获取资源的顺序是不确定的,一般是抢占式的;非公平锁相对公平锁是增加了获取资源的不确定性,但是整体效率得以提升;
基本特点: 整体效率高,线程等待时间片具有不确定性;
5.编程灵活度和难度

        Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。

两者之间区别(和synchronized的区别)

使用方式
Synchronized可以修饰实例方法,静态方法,代码块。自动释放锁。

ReentrantLock一般需要try catch finally语句,在try中获取锁,在finally释放锁。需要手动释放锁。

实现方式

        Synchronized是重量级锁。重量级锁需要将线程从内核态和用户态来回切换。如:A线程切换到B线程,A线程需要保存当前现场,B线程切换也需要保存现场。这样做的缺点是耗费系统资源。

        ReentrantLock是轻量级锁。采用cas+volatile管理线程,不需要线程切换切换,获取锁线程觉得自己肯定能成功,这是一种乐观的思想(可能失败)。

        需要注意的是:这是两种不一样的思维方式,​前者是被动阻塞悲观锁,状态是block,后者是主动的阻塞乐观锁,状态是wait。

公平和非公平
Synchronized只有非公平锁。

ReentrantLock提供公平和非公平两种锁,默认是非公平的。公平锁通过构造函数传递true表示。

用一个形象例子来说明:排队打饭,Synchronized允许插队,如果ReentrantLock是公平锁,就不许插队了。

可重入锁
        Synchronized和ReentrantLock都是可重入的,Synchronized是本地方法是C++实现,而ReentrantLock是JUC包用Java实现。

        用一个形象例子来说明:如下图:一个房中房,房里外各有一把锁,但只有唯一的钥匙可以开,拥有钥匙的人可以先进入门1,再进入门2,其中进入门2就是叫锁可重入了。

在ReentrantLock中,重入次数用整形state表示。进入1次递增1次,出来1次递减1次。

可中断的
        Synchronized是不可中断的。

        ReentrantLock提供可中断和不可中断两种方式。其中lockInterruptibly方法表示可中断,lock方法表示不可中断。

条件队列
Synchronized只有一个等待队列。

ReentrantLock中一把锁可以对应多个条件队列。通过newCondition表示。

        用一个形象例子来说明:母鸡下蛋和捡蛋人对应生产者和消费者,母鸡产蛋后,捡蛋人需要被母鸡通知,母鸡产蛋过程中,其中捡蛋人就会入条件队列(等待队列)。捡蛋人捡蛋完成后,捡蛋人需要通知母鸡继续产蛋,捡蛋人捡蛋过程中,母鸡也需要加入条件队列等待。

注意:有几个概念需要说明下。同步队列,条件队列和等待队列。

同步队列:多线程同时竞争一把锁失败被挂起的线程。

条件队列:正在执行的线程调用await/wait,从同步队列加入的线程会进入条件队列。正在执行线程调用signal/signalAll/notify/notifyAll,会将条件队列一个线程或多个线程加入到同步队列。

synchronized锁升级

synchronized锁有四种状态,无锁,偏向锁,轻量级锁,重量级锁,这几个状态会随着竞争状态逐渐升级,锁可以升级但不能降级,但是偏向锁状态可以被重置为无锁状态

偏向锁
为什么要引入偏向锁?

        因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。

偏向锁原理和升级过程

        当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;

        如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,

        如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;

        如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

轻量级锁
为什么要引入轻量级锁?

        轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

轻量级锁原理和升级过程

        线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;

        如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。 自旋锁简单来说就是让线程2在循环中不断CAS

        但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

几种锁的优缺点?

错误的加锁姿势1

synchronized (new Object())


每次调用创建的是不同的锁,相当于无锁

错误的加锁姿势2

private Integer count;
synchronized (count)

        String,Boolean在实现了都用了享元模式,即值在一定范围内,对象是同一个。所以看似是用了不同的对象,其实用的是同一个对象。会导致一个锁被多个地方使用。

正确的加锁姿势

// 普通对象锁
private final Object lock = new Object();
// 静态对象锁
private static final Object lock = new Object();

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/459470.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号