Java并发编程实战
王宝令
资深架构师
立即订阅
15077 人已学习
课程目录
已完结 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并发编程实战
登录|注册

31 | Guarded Suspension模式:等待唤醒机制的规范实现

王宝令 2019-05-09
前不久,同事小灰工作中遇到一个问题,他开发了一个 Web 项目:Web 版的文件浏览器,通过它用户可以在浏览器里查看服务器上的目录和文件。这个项目依赖运维部门提供的文件浏览服务,而这个文件浏览服务只支持消息队列(MQ)方式接入。消息队列在互联网大厂中用的非常多,主要用作流量削峰和系统解耦。在这种接入方式中,发送消息和消费结果这两个操作之间是异步的,你可以参考下面的示意图来理解。
消息队列(MQ)示意图
在小灰的这个 Web 项目中,用户通过浏览器发过来一个请求,会被转换成一个异步消息发送给 MQ,等 MQ 返回结果后,再将这个结果返回至浏览器。小灰同学的问题是:给 MQ 发送消息的线程是处理 Web 请求的线程 T1,但消费 MQ 结果的线程并不是线程 T1,那线程 T1 如何等待 MQ 的返回结果呢?为了便于你理解这个场景,我将其代码化了,示例代码如下。
class Message{
String id;
String content;
}
//该方法可以发送消息
void send(Message msg){
//省略相关代码
}
//MQ消息返回后会调用该方法
//该方法的执行线程不同于
//发送消息的线程
void onMessage(Message msg){
//省略相关代码
}
//处理浏览器发来的请求
Respond handleWebReq(){
//创建一消息
Message msg1 = new
Message("1","{...}");
//发送消息
send(msg1);
//如何等待MQ返回的消息呢?
String result = ...;
}
看到这里,相信你一定有点似曾相识的感觉,这不就是前面我们在《15 | Lock 和 Condition(下):Dubbo 如何用管程实现异步转同步?》中曾介绍过的异步转同步问题吗?仔细分析,的确是这样,不过在那一篇文章中我们只是介绍了最终方案,让你知其然,但是并没有介绍这个方案是如何设计出来的,今天咱们再仔细聊聊这个问题,让你知其所以然,遇到类似问题也能自己设计出方案来。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(44)

  • 青莲
    sleep 无法被唤醒,只能时间到后自己恢复运行,当真正的条件满足了,时间未到,接着睡眠,无性能可言
    2019-05-09
    25
  • 代码搬运工
    当从消息队列接收消息失败时,while循环会一直执行下去,永远不会结束,回占用大量资源。

    作者回复: 👍

    2019-05-09
    15
  • zhangwei
    老师,我有个疑问,希望帮忙解答。如果Web应用是集群的,A节点处理HTTP请求后发了MQ,B节点的onMessage消费了回执消息,那么A节点怎么把结果响应给客户端呢?疑问好久了,希望老师给个思路,谢谢!

    作者回复: 我了解有人是这么做的:把回执消息放到redis的list中,按照ip重新分组之后从redis中再次消费。
    也可以按照ip建立不同的topic。

    2019-05-18
    1
    12
  • Felix Envy
    老师,感觉如果有方法调用了GuardedObect.create方法但是没有任何其他线程调用fireEvent方法会造成内存泄漏啊,这种情况需要考虑吗?

    作者回复: 👍 需要,等待超时后要把他移除。

    2019-05-12
    7
  • linqw
    总结:Guarded Suspension模式,要解决的是,发送消息的线程和消费消息结果的线程不是同一个,但是消息结果又需要由发送的线程进行处理,为此需要为每个消息创建出类似大堂经理,生活中一般是只有一个大堂经理,但是在编程世界里需要为每个分配一个大堂经理,大堂经理主要做的事情就是发送线程发送完消息时,将其阻塞,提供消息结果的回调接口,通知阻塞的发送线程消费消息结果。
    课后习题:1、使用sleep如果消息结果已经返回,还需等到sleep超时,才能继续执行2、使用加锁的await方法可以保证可见性,如果使用sleep的话,需要给obj加上volatile3、感觉在sent(message)成功后才阻塞,不然有可能mq接收消息失败,while循环会一直执行下去,sent失败,直接响应提示给前端。
    2019-05-25
    6
  • Mr.Brooks
    没有锁也无法保证内存可见性吧

    作者回复: 👍

    2019-05-10
    6
  • 张三
    接入微信支付支付宝支付里边,也需要提供一个回调函数,onChange()就是一个回调函数吧,不过微信支付宝支付是异步回调,是不是也可以改成这种?微信支付宝里边的其它第三方支付是不是就是这种模式,因为支付成功之后跳转到它们自己的页面,而不是微信支付宝官方的支付成功界面

    作者回复: 这个回调函数和mq的回调函数从服务接入方的角度看是一样的

    2019-05-10
    2
  • 晓杰
    用sleep的话只能等睡眠时间到了之后再返回while循环条件去判断,但是wait相当于和singal组成等待唤醒的机制,这样满足条件的概率更大一些,性能也更好
    2019-05-09
    2
  • zero
    wait会释放占有的资源,sleep不会释放
    2019-05-09
    2
  • 杨鹏程baci
    老师好,Message r = get(t->t != null); 这个地方里面的t是什么参数啊,有点看的不是很懂,然后在get方法里面!p.test(obj)里面是判断了啥,就这里有点看的不是很明白,谢谢!
    2019-07-05
    1
  • null
    老师,您好!
    我想到了一个场景:​线程 t1 提交了消息 m1,线程 t2 提交了消息 m2,此时都在 get() 方法处等待结果返回。m2 先被处理完,this.obj 对应的是消息 m2 的结果,调用 fireEvent() 唤醒 t1 和 t2,t1 竞争到锁资源,消费了 m2 的结果 this.obj。

    如果存在这种场景,再维护一个 ConcurrentHashMap,key 是 msg.id,value 是对应的 obj,是否就能解决结果这问题?

    谢谢老师!

    作者回复: 只要唤醒的时候能找到正确的线程就可以,不知道你的方法是不是能做到

    2019-06-03
    1
    1
  • Rancood
    这个模式了解了,但是实例中业务有点懵,handleWebReq方法最终拿到的是自己发送出去的message消息;是不是应该在onMessage方法唤醒等待线程之前进行业务处理,生成新的Message消息newMsg,然后把newMsg传到fireEvent里面,这样拿到的是反馈的结果。请老师指点一下
    2019-05-22
    1
    1
  • 朱晋君
    如果以文中的最后一段示例代码来看,每一个请求生成一个id,对应一个GuardedObject,并没有线程安全问题。我觉得可以去掉锁。
    但是加sleep的话,没有办法唤醒,只能等到超时。

    作者回复: await和notify获取锁才能调用,所以不能去掉锁

    2019-05-09
    1
  • 石头🍊
    使用LockSupport配合map是不是也可以实现
    2019-11-26
  • 放个屁臭到了自己
    sleep 不是释放锁
    2019-11-25
  • Joker
    如果按照代码上面来看,这还是一个客户对应一个大堂经理了呗。。每次还要换新的大堂经理的感觉。
    2019-11-08
  • 赤城
    请问老师,真正消息队列的请求中,如果是需要有消息处理返回值的情况,就是采用这种模式实现的吗,例如RabbitMQ

    作者回复: 除了这个办法好像没别的办法了😂

    2019-11-06
  • 墨飞域
    老师,这节听了个大概,不是非常懂。其中有一点没理解,get方法加锁后,while判断一直都为true,也就一直不会释放锁,那onChanged方法进去之后,获取不到锁,双方不久互相死等下去了么,我应该还是哪里没想明白

    作者回复: wait会释放锁,建议重看第一部分管程相关的内容

    2019-10-30
  • Sharry
    看了这篇文章之后才发现, 在使用管程解决并发编程的协作问题时, Guarded Suspension 模型几乎无处不在, 它太常用了, 没想到它居然已经抽象成一种编程模式.
    2019-09-29
  • DFighting
    问题的原因主要是sleep没办法提供等待-唤醒机制,也就是说极端情况下可能会一直处于睡眠状态。
    老师,有个问题,为什么sleep不能被唤醒啊?网上查了下答案,好像是说wait会持有一个对象锁(JVM)提供的,然后在执行notify操作的时候,JVM会释放这个对象锁,并选择一个等待的线程执行。而sleep并没有释放这个锁,所以没办法唤醒?感觉有点道理,但是不知道对不对,望老师不吝赐教

    作者回复: wait是管程里的操作,sleep就是一个系统api,他们其实没关系,所以sleep不会释放管程的锁,他们没关系

    2019-09-28
收起评论
44
返回
顶部