人的一生就像在攀登高峰,勤奋是你踏实稳健的双脚,信念是你指引前行的向导,勇敢是你孜孜追寻的恒心。开心日到了,愿你站稳双脚,确定方向,向着你的理想巅峰勇敢前行,不用怕,未来就在你的脚下。
作为一名面试官,我在面试别人的时候,经常反复拿来问别人的一个问题,最近详细整理了一下,跟大家分享一下。总结的不好的地方或者有不同见解的地方欢迎大家私聊我一起探讨。
1. 为什么引入StampedLock
JDK8中新增StampedLock。
从ReentrantLock到ReentrantReadWriteLock,再到StampedLock,读操作并发度依次提高。
ReentrantReadWriteLock采用“悲观读”策略,当第一个读线程抢到共享锁,第二个、第三个读线程还可以抢到共享锁,使得写线程一直无法获取互斥写锁,会导致写线程“饿死”。
读写锁的公平/非公平实现中,尽量避免这种情形,但还有可能发生。
StampedLock通过读写不互斥进一步提高读的并发量。
读写不互斥的问题在于:会产生不可重复读,两次读取结果不一样。
即读取的时候,可能另一个线程正在修改该值,还没有完成,读完之后,写线程也操作结束,此时读线程读到的数据和真实的数据不一致。
2. StampedLock的解决方式
StampedLock引入了“乐观读”策略:
- 读的时候不加读锁,读出来发现数据被修改了,再升级为“悲观读”;即读错了再严格读一次,避免写线程被饿死。
- StampedLock是一个读写锁,悲观读和悲观写的锁状态需要同步,互斥,锁状态操作需要是原子操作。
首先需要在state中划分出读锁和写锁。
3. state中的具体表示
state用于表示锁状态:
private transient volatile long state;
第1-7位记录共享锁读锁,每获取一个读锁,该7位+1,如果溢出,每溢出一次,readerOverflow+1;
每释放一个读锁,该值-1,如果不够,借位readerOverflow的值。
第8位记录互斥锁写锁状态,如果存在写锁,该位记1,不存在记0;
第8-64位用于记录乐观锁版本号。
每获取一次写锁,state+WBIT,即从第8位开始的值。释放写锁,并不让state-WBIT,保证乐观锁版本号的单调递增,防止乐观锁出现ABA问题,即在tryOptimisticRead
和validate(stamp)
之间获取写锁并释放写锁。
如果从第8位到第64位都是1,则再加WBIT,将导致state是0,因为64位越界,此时将state复位ORIGIN:
即:
获取写锁的时候,state+WBIT,释放的时候state+WBIT,第一次让写锁位置位1,第二次让写锁位置位0,同时乐观锁位表示的值增大。
当写锁位是1的时候,悲观读锁位都是0,readerOverflow也是0。
当写锁位是0的时候,如果悲观锁抢锁,则写锁位的7位以及readerOverflow用于记录cowait链表获取共享读锁的状态。
4. StampedLock流程
4.1 tryOptimisticRead方法
先判断是否存在写锁,如果存在,就返回0L,表示乐观锁获取失败。
如果不存在写锁,就获取此时的乐观锁版本号,以用于稍后的validate(stamp)校验乐观锁的成功与否。
4.2 悲观读锁以及StampedLock实现的CLH队列
WMODE是互斥锁,链表中是单节点,RMODE是共享读锁,使用cowait进行横链连接同是RMODE的节点。
最近收集整理一些面试资料,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等技术点,有兴趣的同学可以+VX(lagou2021) 获取相关资料
福利
海量互联网大厂面试真题详解与大家分享,共同学习。 职业规划、简历指导、Java实用书籍、各种Java学习视频、技能的学习、思维的培养。 感兴趣的小伙伴可以关注一下我的CSDN账号,私聊获取个类资料。(或者微信账号:lagou2021)
附赠1: 10G开发相关电子书,总有一款适合你,私聊获取
附赠2:成功入职大厂60k的简历模板,私聊获取