阿里四轮面试遭遇StampedLock,这么应对保拿offer!(1)

我们知道ReadWriteLock支持读锁、写锁两种锁模式。而StampedLock支持三种:写锁、悲观读锁和乐观读。

其写锁、悲观读锁和ReadWriteLock的写锁、读锁的语义类似。不同在于:StampedLock的写锁和悲观读锁加锁成功后,都会返回一个stamp;释放锁时,需要传入该stamp。

那StampedLock的性能为啥比ReadWriteLock好呢?

核心在于StampedLock支持乐观读。ReadWriteLock支持多个线程同时读,但当多线程读时,所有的写操作会被阻塞;而StampedLock提供的乐观读,是允许一个线程获取写锁的,也就是说不是所有写操作都被阻塞。

乐观读的操作是无锁的,所以相比较ReadWriteLock的读锁,乐观读性能更好。

那你工作中一般如何使用的呢?

该示例中,若执行乐观读过程中,存在写操作,会把乐观读升级为悲观读锁。这样很好,否则就要在一个循环里反复执行乐观读,直到执行乐观读操作的期间没有写操作(这样才能保证x和y的正确性和一致性),而循环读会浪费大量CPU。升级为悲观读锁,代码简练且不易出错。

谈谈你对乐观读的理解?

很多人喜欢类比StampedLock的乐观读和数据库的乐观锁。

数据库乐观锁使用场景是这样的:一个模块,会有多个人通过前端同时修改同一条订单,那如何保证订单数据是线程安全的呢?

这就可以使用乐观锁。

乐观锁实现很简单,在订单的表 product_doc 增加一个数值类型版本号字段 version,每次更新product_doc表时,将 version 字段加1。生产订单的UI在展示的时候,需要查询数据库,此时将这个 version 字段和其他业务字段一起返回给生产订单UI。

假设用户查询的生产订单的id=777,SQL如下:

select id,… ,version

from product_doc

where id=777

用户在前端执行保存操作的时候,后台利用下面的SQL语句更新生产订单,此处我们假设该条生产订单的 version=9。

update product_doc

set version=version+1,…

where id=777 and version=9

如果这条SQL语句执行成功并且返回1,说明前端执行查询操作到执行保存操作期间,没有其他人修改过这条数据。因为如果这期间其他人修改过这条数据,那么版本号字段一定会大于9。、

数据库里的乐观锁,查询的时候需要把 version 字段查出来,更新的时候要利用 version 字段做验证。这个 version 字段就类似于StampedLock里面的stamp。

StampedLock使用时踩过什么坑吗?

读多写少场景StampedLock性能很好,可替代ReadWriteLock,但StampedLock不可重入。StampedLock的悲观读锁、写锁都不支持条件变量。

如果线程阻塞在StampedLock的readLock()或writeLock(),此时调用该阻塞线程的interrupt(),会导致CPU飙升。

例如下面的代码中,线程T1获取写锁之后将自己阻塞,线程T2尝试获取悲观读锁,也会阻塞;如果此时调用线程T2的interrupt()方法来中断线程T2的话,你会发现线程T2所在CPU会飙升到100%。

final StampedLock lock

= new StampedLock();

Thread T1 = new Thread(()->{

// 获取写锁

lock.writeLock();

// 永远阻塞在此处,不释放写锁

LockSupport.park();

});

T1.start();

// 保证T1获取写锁

Thread.sleep(100);

Thread T2 = new Thread(()->

//阻塞在悲观读锁

lock.readLock()

);

T2.start();

// 保证T2阻塞在读锁

Thread.sleep(100);

//中断线程T2

//会导致线程T2所在CPU飙升

T2.interrupt();

T2.join();

所以使用StampedLock一定不要调用中断。如果需要支持中断功能,一定使用可中断的悲观读锁readLockInterruptibly()和写锁writeLockInterruptibly()。

总结

=================================================================

StampedLock的使用看上去有点复杂,但是如果你能理解乐观锁背后的原理,使用起来还是比较流畅的。建议你认真揣摩Java的官方示例,这个示例基本上就是一个最佳实践。我们把Java官方示例精简后,形成下面的代码模板,建议你在实际工作中尽量按照这个模板来使用StampedLock。

StampedLock读模板:

final StampedLock sl =

new StampedLock();

最后

ActiveMQ消息中间件面试专题

  • 什么是ActiveMQ?
  • ActiveMQ服务器宕机怎么办?
  • 丢消息怎么办?
  • 持久化消息非常慢怎么办?
  • 消息的不均匀消费怎么办?
  • 死信队列怎么办?
  • ActiveMQ中的消息重发时间间隔和重发次数吗?

ActiveMQ消息中间件面试专题解析拓展:

BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM


redis面试专题及答案

  • 支持一致性哈希的客户端有哪些?
  • Redis与其他key-value存储有什么不同?
  • Redis的内存占用情况怎么样?
  • 都有哪些办法可以降低Redis的内存使用情况呢?
  • 查看Redis使用情况及状态信息用什么命令?
  • Redis的内存用完了会发生什么?
  • Redis是单线程的,如何提高多核CPU的利用率?

BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM


Spring面试专题及答案

  • 谈谈你对 Spring 的理解
  • Spring 有哪些优点?
  • Spring 中的设计模式
  • 怎样开启注解装配以及常用注解
  • 简单介绍下 Spring bean 的生命周期

Spring面试答案解析拓展

BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM


高并发多线程面试专题

  • 现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行?
  • Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。
  • Java 中 wait 和 sleep 方法有什么区别?
  • 如何在 Java 中实现一个阻塞队列?
  • 如何在 Java 中编写代码解决生产者消费者问题?
  • 写一段死锁代码。你在 Java 中如何解决死锁?

高并发多线程面试解析与拓展

BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM


jvm面试专题与解析

  • JVM 由哪些部分组成?
  • JVM 内存划分?
  • Java 的内存模型?
  • 引用的分类?
  • GC什么时候开始?

JVM面试专题解析与拓展!

BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM

锁代码。你在 Java 中如何解决死锁?

高并发多线程面试解析与拓展

[外链图片转存中…(img-5a2yaiGp-1714824857550)]


jvm面试专题与解析

  • JVM 由哪些部分组成?
  • JVM 内存划分?
  • Java 的内存模型?
  • 引用的分类?
  • GC什么时候开始?

JVM面试专题解析与拓展!

[外链图片转存中…(img-n7d0iw84-1714824857550)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值