可重入锁(ReentrantLock和synchronized原理及区别)+锁升级

目录

1.Synchronized底层原理

ReentrantLock实现的原理及使用:

Java中synchronized 和 ReentrantLock 有什么不同?

追问3:synchronized锁升级的过程说一下?

追问4:synchronize锁的作用范围


可重入锁

一、基本概念和使用

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

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

1.Synchronized底层原理

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

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

ReentrantLock实现的原理及使用:

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

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

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

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

Java中synchronized 和 ReentrantLock 有什么不同?

相同点:

        它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的.

区别:

        对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。Synchronized可以修饰实例方法,静态方法,代码块。自动释放锁。

        而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。需要try catch finally语句,在try中获取锁,在finally释放锁。需要手动释放锁。

        Synchronized经过编译,会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。
        由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized, ReentrantLock类提供了一些高级功能,主要有以下3项:

        1.    等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。
        2.    公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁, ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
        3.    锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。

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中声明释放锁。

追问3:synchronized锁升级的过程说一下?

回答:在jdk1.6后Java对synchronize锁进行了升级过程,主要包含偏向锁、轻量级锁和重量级锁,主要是针对对象头MarkWord的变化。

(1)偏向锁

为什么要引入偏向锁?

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

偏向锁的升级

当线程1访问代码块并获取锁对象时,会在java对象头栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

(2)轻量级锁

为什么要引入轻量级锁?

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

轻量级锁什么时候升级为重量级锁?

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

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

但是如果自旋的时间太长也不行,因为自旋是要消耗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();

追问4:synchronize锁的作用范围

回答:

(1)synchronize作用于成员变量和非静态方法时,锁住的是对象的实例,即this对象。

(2)synchronize作用于静态方法时,锁住的是Class实例

(3)synchronize作用于一个代码块时,锁住的是所有代码块中配置的对象。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值