【并发编程】(学习笔记-共享模型之JUC-读写锁原理)-part6

}

}

输出结果,从这里可以看到 Thread-0 锁定期间,Thread-1 的读操作不受影响:

image-20220112143719696

测试 读锁-写锁 相互阻塞:

@Slf4j

public class TestReadWriteLock {

public static void main(String[] args) throws InterruptedException {

Datacontainer datacontainer = new Datacontainer();

new Thread(() -> {

datacontainer.read();

}, “t1”).start();

Thread.sleep(100);

new Thread(() -> {

datacontainer.write();

}, “t2”).start();

}

}

结果:

image-20220112143831543

注意:

  • 读锁不支持条件变量
  • 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
  • 重入时降级支持:即持有写锁的情况下去获取读锁

看一个重入降级获得锁栗子:

class CachedData {

Object data;

// 是否有效,如果失效,需要重新计算 data

volatile boolean cacheValid;

final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

void processCachedData() {

rwl.readLock().lock();

if (!cacheValid) {

// 获取写锁前必须释放读锁

rwl.readLock().unlock();

rwl.writeLock().lock();

try {

// 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新

if (!cacheValid) {

data = …

cacheValid = true;

}

// 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存

rwl.readLock().lock();

} finally {

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);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

面试难免让人焦虑不安。经历过的人都懂的。但是如果你提前预测面试官要问你的问题并想出得体的回答方式,就会容易很多。

此外,都说“面试造火箭,工作拧螺丝”,那对于准备面试的朋友,你只需懂一个字:刷!

给我刷刷刷刷,使劲儿刷刷刷刷刷!今天既是来谈面试的,那就必须得来整点面试真题,这不花了我整28天,做了份“Java一线大厂高岗面试题解析合集:JAVA基础-中级-高级面试+SSM框架+分布式+性能调优+微服务+并发编程+网络+设计模式+数据结构与算法等”

image

且除了单纯的刷题,也得需准备一本【JAVA进阶核心知识手册】:JVM、JAVA集合、JAVA多线程并发、JAVA基础、Spring 原理、微服务、Netty与RPC、网络、日志、Zookeeper、Kafka、RabbitMQ、Hbase、MongoDB、Cassandra、设计模式、负载均衡、数据库、一致性算法、JAVA算法、数据结构、加密算法、分布式缓存、Hadoop、Spark、Storm、YARN、机器学习、云计算,用来查漏补缺最好不过。

image

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
来整点面试真题,这不花了我整28天,做了份“Java一线大厂高岗面试题解析合集:JAVA基础-中级-高级面试+SSM框架+分布式+性能调优+微服务+并发编程+网络+设计模式+数据结构与算法等”

[外链图片转存中…(img-AdCgST2X-1713547922354)]

且除了单纯的刷题,也得需准备一本【JAVA进阶核心知识手册】:JVM、JAVA集合、JAVA多线程并发、JAVA基础、Spring 原理、微服务、Netty与RPC、网络、日志、Zookeeper、Kafka、RabbitMQ、Hbase、MongoDB、Cassandra、设计模式、负载均衡、数据库、一致性算法、JAVA算法、数据结构、加密算法、分布式缓存、Hadoop、Spark、Storm、YARN、机器学习、云计算,用来查漏补缺最好不过。

[外链图片转存中…(img-oQXFMf8L-1713547922355)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值