最新【并发编程】(学习笔记-共享模型之JUC-读写锁原理)-part6(1),深度解读Netty

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

最新整理面试题
在这里插入图片描述

上述的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题

最新整理电子书

在这里插入图片描述

最新整理大厂面试文档

在这里插入图片描述

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

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

需要这份系统化的资料的朋友,可以点击这里获取

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 位

image-20220112131402787

2)t2 执行 r.lock,这时进入读锁的 sync.acquireShared(1) 流程,首先会进入 tryAcquireShared 流程。如果有写锁占据,那么 tryAcquireShared 返回 -1 表示失败

tryAcquireShared 返回值表示:

  • -1 表示失败
  • 0 表示成功
  • 正数表示成功(而且数值是还有几个后继节点需要唤醒,读写锁返回1)

image-20220112131658507

3)这时会进入 sync.doAcquireShared(1) 流程,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为 Node.SHARED 模式而非 Node.EXCLUSIVE 模式,注意此时 t2 仍处于活跃状态

image-20220112131849544

4)t2 会看看自己的节点是不是老二,如果是,还会再次调用 tryAcquireShared(1) 来尝试获取锁

5)如果没有成功,在 doAcquireShared 内 for (;;) 循环一次,把前驱节点的 waitStatus 改为 -1,再 for (;😉 循环一 次尝试 tryAcquireShared(1) 如果还不成功,那么在 parkAndCheckInterrupt() 处 park

image-20220112132525925

t3 r.lock,t4 w.lock

这种状态下,假设又有 t3 加读锁和 t4 加写锁,这期间 t1 仍然持有锁,就变成了下面的样子

image-20220112132554959

t1 w.unlock

这时会走到写锁的 sync.release(1) 流程,调用 sync.tryRelease(1) 成功,变成下面的样子

image-20220112132644439

接下来执行唤醒流程 sync.unparkSuccessor,即让老二恢复运行,这时 t2 在 doAcquireSharedparkAndCheckInterrupt 处恢复运行 这回再来一次 for (;;) 执行 tryAcquireShared 成功则让读锁计数加一

image-20220112132747732

这时 t2 已经恢复运行,接下来 t2 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点

image-20220112132829768

事情还没完,在 setHeadAndPropagate 方法内还会检查下一个节点是否是 shared,如果是则调用 doReleaseShared() 将 head 的状态从 -1 改为 0 并唤醒老二,这时 t3 在 doAcquireSharedparkAndCheckInterrupt处恢复运行

image-20220112132943597

这回再来一次 for (;;) 执行 tryAcquireShared 成功则让读锁计数加一

image-20220112133031875

这时 t3 已经恢复运行,接下来 t3 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点

image-20220112143111996

下一个节点不是 shared 了,因此不会继续唤醒 t4 所在节点

t2 r.unlock,t3 r.unlock

t2 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,但由于计数还不为零

image-20220112133521627

t3 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,这回计数为零了,进入 doReleaseShared() 将头节点从 -1 改为 0 并唤醒老二,即

image-20220112133755274

之后 t4 在 acquireQueuedparkAndCheckInterrupt 处恢复运行,再次 for (;;) 这次自己是老二,并且没有其他竞争,tryAcquire(1) 成功,修改头结点,流程结束

image-20220112134218605

2.StampedLock


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();

}

}

输出结果,可以看到实际没有加读锁:

image-20220112125524741

测试 读-写 时优化读补加读锁:

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来兜底,以便在最坏情况发生时仍然能够从容应对。笔记的最后,将带你思考可以从哪些环节来设计兜底方案。


篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),麻烦各位转发一下(可以帮助更多的人看到哟!)

由于内容太多,这里只截取部分的内容。

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

需要这份系统化的资料的朋友,可以点击这里获取

,所以要保证系统的高可用和正确性,还要设计一个PlanB来兜底,以便在最坏情况发生时仍然能够从容应对。笔记的最后,将带你思考可以从哪些环节来设计兜底方案。


篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),麻烦各位转发一下(可以帮助更多的人看到哟!)

[外链图片转存中…(img-C4RsGIBg-1715656278309)]

[外链图片转存中…(img-yRB3ahOC-1715656278310)]

由于内容太多,这里只截取部分的内容。

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

需要这份系统化的资料的朋友,可以点击这里获取

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值