Java并发编程实战
王宝令
资深架构师
立即订阅
15151 人已学习
课程目录
已完结 50 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 你为什么需要学习并发编程?
免费
学习攻略 (1讲)
学习攻略 | 如何才能学好并发编程?
第一部分:并发理论基础 (13讲)
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
第二部分:并发工具类 (14讲)
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
第三部分:并发设计模式 (10讲)
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
第四部分:案例分析 (4讲)
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
第五部分:其他并发模型 (4讲)
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
结束语 (1讲)
结束语 | 十年之后,初心依旧
用户故事 (2讲)
用户来信 | 真好,面试考到这些并发编程,我都答对了!
3 个用户来信 | 打开一个新的并发世界
Java并发编程实战
登录|注册

17 | ReadWriteLock:如何快速实现一个完备的缓存?

王宝令 2019-04-06
前面我们介绍了管程和信号量这两个同步原语在 Java 语言中的实现,理论上用这两个同步原语中任何一个都可以解决所有的并发问题。那 Java SDK 并发包里为什么还有很多其他的工具类呢?原因很简单:分场景优化性能,提升易用性
今天我们就介绍一种非常普遍的并发场景:读多写少场景。实际工作中,为了优化性能,我们经常会使用缓存,例如缓存元数据、缓存基础数据等,这就是一种典型的读多写少应用场景。缓存之所以能提升性能,一个重要的条件就是缓存的数据一定是读多写少的,例如元数据和基础数据基本上不会发生变化(写少),但是使用它们的地方却很多(读多)。
针对读多写少这种并发场景,Java SDK 并发包提供了读写锁——ReadWriteLock,非常容易使用,并且性能很好。
那什么是读写锁呢?
读写锁,并不是 Java 语言特有的,而是一个广为使用的通用技术,所有的读写锁都遵守以下三条基本原则:
允许多个线程同时读共享变量;
只允许一个线程写共享变量;
如果一个写线程正在执行写操作,此时禁止读线程读共享变量。
读写锁与互斥锁的一个重要区别就是读写锁允许多个线程同时读共享变量,而互斥锁是不允许的,这是读写锁在读多写少场景下性能优于互斥锁的关键。但读写锁的写操作是互斥的,当一个线程在写共享变量的时候,是不允许其他线程执行写操作和读操作。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(72)

  • 密码123456
    有多少跟我一样,发的内容能够看的懂。一到思考题,要么不会,要么心里的答案答非所问。
    2019-04-06
    5
    118
  • linqw
    1、课后习题感觉可以使用第一种方法:①ps -ef | grep java查看pid②top -p查看java中的线程③使用jstack将其堆栈信息保存下来,查看是否是锁升级导致的阻塞问题。第二种方法:感觉可以调用下有获取只有读锁的接口,看下是否会阻塞,如果没有阻塞可以在调用下写锁的接口,如果阻塞表明有读锁。
    2、读写锁也是使用volatile的state变量+加上happens-before来保证可见性么?
    3、写下缓存和数据库的一致性问题的理解,如果先写缓存再写数据库,使用分布式锁,如果先写数据库再写缓存,①比如文中所说的使用binlog,canal+mq,但是感觉这个还得看具体情况,有可能binlog使用了组提交,不是马上写的binlog文件中,感觉也是会有延迟②感觉也可以使用定时任务定时的扫描任务表③使用消息队列
    2019-04-07
    24
  • crazypokerk
    老师,可不可以这样理解,ReadWirteLock不支持锁的升级,指的是:在不释放读锁的前提下,无法继续获取写锁,但是如果在释放了读锁之后,是可以升级为写锁的。锁的降级就是:在不释放写锁的前提下,获取读锁是可以的。请老师指正,感谢。

    作者回复: 可以这样理解,不过释放了读锁,也就谈不上升级了

    2019-04-06
    16
  • 西西弗与卡夫卡
    考虑到是线上应用,可采用以下方法
    1. 源代码分析。查找ReentrantReadWriteLock在项目中的引用,看下写锁是否在读锁释放前尝试获取
    2. 如果线上是Web应用,应用服务器比如说是Tomcat,并且开启了JMX,则可以通过JConsole等工具远程查看下线上死锁的具体情况

    作者回复: 👍

    2019-04-06
    1
    16
  • 缪文@场景鹿
    老师,感觉这里的读写锁,性能还有可以提升的地方,因为这里可能很多业务都会使用这个缓存懒加载,实际生产环境,写缓存操作可能会比较多,那么不同的缓存key,实际上是没有并发冲突的,所以这里的读写锁可以按key前缀拆分,即使是同一个key,也可以类似ConcurrentHash 一样分段来减少并发冲突

    作者回复: 可以这样

    2019-04-07
    15
  • ycfHH
    问题1:获取写锁的前提是读锁和写锁均未被占用?
    问题2:获取读锁的前提是没有其他线程占用写锁?
    基于以上两点所以只支持锁降级而不允许锁升级。
    问题3
    高并发下,申请写锁时是不是中断其他线程申请读锁,然后等待已有读锁全部释放再获取写锁?因为如果没有禁止读锁的申请的话在读多写少的情况下写锁可能一直获取不到。
    这块不太懂,希望老师能指点一下。

    作者回复: 获取写锁的前提是读锁和写锁均未被占用
    获取读锁的前提是没有其他线程占用写锁
    申请写锁时不中断其他线程申请读锁
    公平锁如果过有写申请,能禁止读锁

    2019-05-07
    1
    10
  • xuery
    读锁不能升级为写锁:好理解,本线程在释放读锁之前,想要获取写锁是不一定能获取到的,因为其他线程可能持有读锁(读锁共享),可能导致阻塞较长的时间,所以java干脆直接不支持读锁升级为写锁。
    写锁可以降级为读锁:也好理解,本线程在释放写锁之前,获取读锁一定是可以立刻获取到的,不存在其他线程持有读锁或者写锁(读写锁互斥),所以java允许锁降级
    2019-05-01
    2
    10
  • WL
    老师我们现在的项目全都是集群部署, 感觉在这种情况下是不是单机的Lock,和Synchronized都用不上, 只能采用分布式锁的方案? 那么这种情况下, 如何提高每个实例的并发效率?

    作者回复: 分布式有分布式的锁,单机的效率就是靠多线程了

    2019-04-09
    6
  • Dylan
    一般都说线程池有界队列使用ArrayBlockingQueue,无界队列使用LinkedBlockingQueue,我很奇怪,有界无界不是取决于创建的时候传不传capacity参数么,我现在想创建线程池的时候,new LinkedBlockingQueue(2000)这样定义有界队列,请问可以吗?

    作者回复: 可以,ArrayBlockingQueue有界是因为必须传capacity参数,LinkedBlockingQueue传capacity参数就是有界,不传就是无界

    2019-04-06
    5
  • iron_man
    王老师,写锁降级为读锁的话,前面的写锁是释放了么?后面可不可以讲一下这个读写锁的实现机制呢,这样可以对这种锁有更深入的理解,锁的升级降级也就不会用错了
    2019-04-06
    4
  • 随风而逝
    缓存一致性问题,我们都是双删缓存。老师,读写锁的降级和单独使用有什么区别?或者说有什么优势?

    作者回复: 降级稍微快一点,而且一定能成功。

    2019-04-22
    3
  • 杨鹏程baci
    老师好,我来理解一下,我们对缓存这个例子来说,完全可以用volatile来达到可见性的目的,只是说用了读写锁支持读操作不用写回内存,可以并发执行,只是写操作还是需要每次保持可见性。我还有一个问题,读锁的意义是不是配合写锁时需要线程等待?
    2019-07-01
    2
  • Delong
    用jstack看是不是在waiting自己
    2019-06-08
    2
  • 刘志兵
    这里讲的读写锁和丁奇老师讲的mysql中的mdl锁和ddl锁原理好像是一样的,就是读写互斥,写写互斥,读读不互斥,老师讲的这个应该是读写锁的基本原理,mysql是这个锁的一种典型应用吧

    作者回复: 读写锁本身就是个通用的概念

    2019-04-08
    2
  • 老杨同志
    老师,如果读锁的持有时间较长,读操作又比较多,会不会一直拿不到写锁?

    作者回复: 不会一直拿不到,只是等待的时间会很长

    2019-04-06
    2
  • 密码123456
    系统停止了响应,说明线程可能被占满了。cpu利用率低为什么会推断出,是读锁升级为写锁?是因为锁升级后,线程都是等待状态吗?是不是cpu高是锁竞争?还有怎么验证读锁升级为写锁?

    作者回复: 系统停止了响应,cpu利用率低大概率是死锁了,没法推断,只能大胆假设,小心求证

    2019-04-06
    2
  • 无言的约定
    王老师,""如果一个写线程正在执行写操作,此时禁止读线程读共享变量"" 这句话反过来也成立,是不是意味着读操作和写操作是互斥的,不能同时进行?

    作者回复: 是的,只允许同时读

    2019-12-05
    1
  • 业余草
    读写锁的写操作是互斥的,当一个线程在写共享变量的时候,是不允许其他线程执行写操作和读操作。
    2019-09-18
    1
  • 探索无止境
    老师你好,我们在项目开发中,如果要实现缓存,会直接采用Redis,感觉更合适,所以不太清楚,实际中ReadWriteLock可以解决哪些问题?

    作者回复: 本地缓存更快

    2019-08-26
    1
  • 疯狂咸鱼
    老师您好,我想问下锁的升级/降级有什么好处么?

    作者回复: 一个是出于性能的考虑,一个是防止数据的不一致

    2019-08-08
    1
收起评论
72
返回
顶部