面试那些事(三)

本文详细介绍了Java中的锁升级机制,包括无锁、偏向锁、轻量级锁和重量级锁的转换过程。同时,解释了偏向锁和轻量级锁的设计原理与应用场景,以及CAS操作的工作方式和其存在的ABA问题。此外,还对比分析了synchronized与ReentrantLock的区别。
摘要由CSDN通过智能技术生成

目录

1.多线程的锁升级过程知道是什么样的吗?

2.什么是偏向锁?

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

4.偏向锁的和轻量级锁的升级过程是怎样的?

5.轻量级锁和重量级锁的应用场景。

6.你刚刚提到了cas,给我讲一讲你了解的cas是怎样的?

7.你知道cas的缺陷是什么吗?

8.讲一讲synchronized的实现原理

9.synchronized和ReentrantLock的区别


 

1.多线程的锁升级过程知道是什么样的吗?

Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

2.什么是偏向锁?

偏向锁出现的场景一般是一个线程的情况,一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,下次同一个线程再进入的时候不需要再用cas进行加锁和解锁。提高线程访问的效率。

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

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

4.偏向锁的和轻量级锁的升级过程是怎样的?

偏向锁的升级:当A线程访问代码块并且获取锁的时候,会在java对象头和栈帧中记录该线程的id,当下一次别的线程访问的时候,会比较当前线程的id是否等同于java对象头中的线程id,如果一致,则无需使用cas来加锁解锁。

如果不一致,会根据线程id找到该线程判断该线程是否存活。如果存活,那么立刻查找该线程的栈桢信息,如果该线程还想继续持有,暂停该线程,撤销偏向锁升级轻量锁。

如果不存活。锁状态可以重置为无锁状态。其他线程竞争并将其设置为偏向锁。

轻量级锁的升级:线程A获取轻量级锁的时候,会将锁对象的对象头markdown一份到线程A的栈桢中用于存储锁记录的空间,然后使用cas把对象头的内容替换为锁空间的地址。如果在线程A准备复制对象头信息的时候。线程b也准备获取锁

当线程b再cas替换的时候发现线程A已经把对象头换掉了。那么线程b cas失败。线程b会尝试使用自旋锁来等待A释放锁。

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

5.轻量级锁和重量级锁的应用场景。

轻量级锁适用于少量线程,且业务处理时间比价短暂,追求响应速度的场景。重量级锁适用于大量线程且处理时间较长追求吞吐量的场景。

6.你刚刚提到了cas,给我讲一讲你了解的cas是怎样的?

cas的英文是compare and swap ,cas和volatile是实现并发包的基石,synchronized是一个悲观锁,而cas则是一个乐观锁。

乐观锁的核心是当有多个线程利用cas去更新同一个变量时,只有一个线程能够更新成功,但是其他的线程不会挂起,而是被告知在这次竞争中失败,下次可以再次去竞争。

cas 操作包括三个数,分别是需要读写的位置(v),预期原值(A),新值(B)。如果内存位置的值与预期原值相匹配,则会把内存位置的值更新为值B。

如果内存位置与预期原值的值不匹配,那么处理器不会做任何操作。CAS其实就是一个:我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

7.你知道cas的缺陷是什么吗?

老生常谈的ABA问题。就是线程1进行读取内存变量A的时候,线程2在同一时间也读取了内存变量A,并将A更新为B,这是又将内存的值更新为A,这时线程1进行cas操作成功。但是可能会存在潜藏的问题。

8.讲一讲synchronized的实现原理

synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令。

其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

9.synchronized和ReentrantLock的区别

在synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。synchronized只能随机唤醒一个线程或者全部唤醒。而ReenTrantLock可以根据条件condition分组唤醒线程。ReentrantLock可以实现公平锁和非公平锁的切换。而synchronized是非公平锁。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值