最后
针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。
最新整理面试题
上述的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题
最新整理电子书
最新整理大厂面试文档
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
rwl.writeLock().unlock();
}
}
// 自己用完数据, 释放读锁
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
1.2 原理演示
读写锁用的是同一个 Sycn 同步器,因此等待队列、state 等也是同一个。
t1 w.lock,t2 r.lock
1) t1 成功上锁,流程与 ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了 state 的低 16 位,而读锁使用的是 state 的高 16 位
2)t2 执行 r.lock
,这时进入读锁的 sync.acquireShared
(1) 流程,首先会进入 tryAcquireShared
流程。如果有写锁占据,那么 tryAcquireShared 返回 -1 表示失败
tryAcquireShared 返回值表示:
- -1 表示失败
- 0 表示成功
- 正数表示成功(而且数值是还有几个后继节点需要唤醒,读写锁返回1)
3)这时会进入 sync.doAcquireShared
(1) 流程,首先也是调用 addWaiter
添加节点,不同之处在于节点被设置为 Node.SHARED
模式而非 Node.EXCLUSIVE 模式,注意此时 t2 仍处于活跃状态
4)t2 会看看自己的节点是不是老二,如果是,还会再次调用 tryAcquireShared
(1) 来尝试获取锁
5)如果没有成功,在 doAcquireShared 内 for (;;)
循环一次,把前驱节点的 waitStatus 改为 -1,再 for (;😉 循环一 次尝试 tryAcquireShared(1) 如果还不成功,那么在 parkAndCheckInterrupt
() 处 park
t3 r.lock,t4 w.lock
这种状态下,假设又有 t3 加读锁和 t4 加写锁,这期间 t1 仍然持有锁,就变成了下面的样子
t1 w.unlock
这时会走到写锁的 sync.release
(1) 流程,调用 sync.tryRelease
(1) 成功,变成下面的样子
接下来执行唤醒流程 sync.unparkSuccessor
,即让老二恢复运行,这时 t2 在 doAcquireShared
内 parkAndCheckInterrupt
处恢复运行 这回再来一次 for (;;)
执行 tryAcquireShared
成功则让读锁计数加一
这时 t2 已经恢复运行,接下来 t2 调用 setHeadAndPropagate
(node, 1),它原本所在节点被置为头节点
事情还没完,在 setHeadAndPropagate
方法内还会检查下一个节点是否是 shared
,如果是则调用 doReleaseShared
() 将 head 的状态从 -1 改为 0 并唤醒老二,这时 t3 在 doAcquireShared
内 parkAndCheckInterrupt
处恢复运行
这回再来一次 for (;;)
执行 tryAcquireShared
成功则让读锁计数加一
这时 t3 已经恢复运行,接下来 t3 调用 setHeadAndPropagate
(node, 1),它原本所在节点被置为头节点
下一个节点不是 shared
了,因此不会继续唤醒 t4 所在节点
t2 r.unlock,t3 r.unlock
t2 进入 sync.releaseShared
(1) 中,调用 tryReleaseShared
(1) 让计数减一,但由于计数还不为零
t3 进入 sync.releaseShared
(1) 中,调用 tryReleaseShared
(1) 让计数减一,这回计数为零了,进入 doReleaseShared
() 将头节点从 -1 改为 0 并唤醒老二,即
之后 t4 在 acquireQueued
中 parkAndCheckInterrupt
处恢复运行,再次 for (;;)
这次自己是老二,并且没有其他竞争,tryAcquire
(1) 成功,修改头结点,流程结束
2.1 概述
该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用
加解读锁:
long stamp = lock.readLock();
lock.unlockRead(stamp);
加解写锁:
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
乐观读:StampedLock
支持 tryOptimisticRead()
方法(乐观读),读取完毕后需要做一次戳校验如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。
long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
// 锁升级
}
2.2 例子
提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法:
@Slf4j
class DataContainerStamper {
private int data;
private final StampedLock lock = new StampedLock();
public DataContainerStamper(int data) {
this.data = data;
}
public int read(int readTime) {
//乐观读
long stamp = lock.tryOptimisticRead();
log.debug(“optimistic read lockuing … {}”, stamp);
//模拟读取时间
Sleeper.sleep(readTime);
//校验戳,通过直接返回
if (lock.validate(stamp)) {
log.debug(“read finish… {}”, stamp);
return data;
}
//锁的升级->读锁
log.debug(“updating to read lock… {}”, stamp);
try {
//获取读锁
stamp = lock.readLock();
log.debug(“read lock {}”, stamp);
Sleeper.sleep(readTime);
log.debug(“read finish… {}”, stamp);
return data;
} finally {
log.debug(“read unlock {}”, stamp);
//释放读锁
lock.unlockRead(stamp);
}
}
public void write(int newData) {
//加写锁
long stamp = lock.writeLock();
log.debug(“write lock {}”, stamp);
try {
Sleeper.sleep(2);
this.data = newData;
} finally {
log.debug(“write unlock {}”, stamp);
//释放写锁,需要传入戳
lock.unlockWrite(stamp);
}
}
}
测试 读-读
可以优化:
public class TestStampedLock {
public static void main(String[] args) {
DataContainerStamper dataContainer = new DataContainerStamper(1);
new Thread(() -> {
dataContainer.read(1);
}, “t1”).start();
Sleeper.sleep(0.5);
new Thread(() -> {
dataContainer.read(0);
}, “t2”).start();
}
}
输出结果,可以看到实际没有加读锁:
测试 读-写
时优化读补加读锁:
public class TestStampedLock {
public static void main(String[] args) {
DataContainerStamper dataContainer = new DataContainerStamper(1);
new Thread(() -> {
dataContainer.read(1);
}, “t1”).start();
Sleeper.sleep(0.5);
new Thread(() -> {
dataContainer.write(0);
}, “t2”).start();
}
}
总目录展示
该笔记共八个节点(由浅入深),分为三大模块。
高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。该笔记将从设计数据的动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化这4个方面重点介绍。
一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知。因此,将用一个节点来专门讲解如何设计秒杀减库存方案。
高可用。 虽然介绍了很多极致的优化思路,但现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,还要设计一个PlanB来兜底,以便在最坏情况发生时仍然能够从容应对。笔记的最后,将带你思考可以从哪些环节来设计兜底方案。
篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),麻烦各位转发一下(可以帮助更多的人看到哟!)
由于内容太多,这里只截取部分的内容。
,所以要保证系统的高可用和正确性,还要设计一个PlanB来兜底,以便在最坏情况发生时仍然能够从容应对。笔记的最后,将带你思考可以从哪些环节来设计兜底方案。
篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),麻烦各位转发一下(可以帮助更多的人看到哟!)
[外链图片转存中…(img-C4RsGIBg-1715656278309)]
[外链图片转存中…(img-yRB3ahOC-1715656278310)]
由于内容太多,这里只截取部分的内容。