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并发编程实战
登录|注册

15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?

王宝令 2019-04-02
在上一篇文章中,我们讲到 Java SDK 并发包里的 Lock 有别于 synchronized 隐式锁的三个特性:能够响应中断、支持超时和非阻塞地获取锁。那今天我们接着再来详细聊聊 Java SDK 并发包里的 Condition,Condition 实现了管程模型里面的条件变量
《08 | 管程:并发编程的万能钥匙》里我们提到过 Java 语言内置的管程里只有一个条件变量,而 Lock&Condition 实现的管程是支持多个条件变量的,这是二者的一个重要区别。
在很多并发场景下,支持多个条件变量能够让我们的并发程序可读性更好,实现起来也更容易。例如,实现一个阻塞队列,就需要两个条件变量。
那如何利用两个条件变量快速实现阻塞队列呢?
一个阻塞队列,需要两个条件变量,一个是队列不空(空队列不允许出队),另一个是队列不满(队列已满不允许入队),这个例子我们前面在介绍管程的时候详细说过,这里就不再赘述。相关的代码,我这里重新列了出来,你可以温故知新一下。
public class BlockedQueue<T>{
final Lock lock =
new ReentrantLock();
// 条件变量:队列不满
final Condition notFull =
lock.newCondition();
// 条件变量:队列不空
final Condition notEmpty =
lock.newCondition();
// 入队
void enq(T x) {
lock.lock();
try {
while (队列已满){
// 等待队列不满
notFull.await();
}
// 省略入队操作...
//入队后,通知可出队
notEmpty.signal();
}finally {
lock.unlock();
}
}
// 出队
void deq(){
lock.lock();
try {
while (队列已空){
// 等待队列不空
notEmpty.await();
}
// 省略出队操作...
//出队后,通知可入队
notFull.signal();
}finally {
lock.unlock();
}
}
}
不过,这里你需要注意,Lock 和 Condition 实现的管程,线程等待和通知需要调用 await()、signal()、signalAll(),它们的语义和 wait()、notify()、notifyAll() 是相同的。但是不一样的是,Lock&Condition 实现的管程里只能使用前面的 await()、signal()、signalAll(),而后面的 wait()、notify()、notifyAll() 只有在 synchronized 实现的管程里才能使用。如果一不小心在 Lock&Condition 实现的管程里调用了 wait()、notify()、notifyAll(),那程序可就彻底玩儿完了。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(54)

  • ZOU志伟
    不合理,会导致很多请求超时,看了源码是调用signalAll()

    作者回复: 写这一章的时候还是signal,后来有人提了个bug,就改成signalall了

    2019-04-03
    2
    53
  • 张天屹
    我理解异步的本质是利用多线程提升性能,异步一定是基于一个新开的线程,从调用线程来看是异步的,但是从新开的那个线程来看,正是同步(等待)的,只是对于调用方而言这种同步是透明的。正所谓生活哪有什么岁月静好,只是有人替你负重前行。

    作者回复: 总结的太有文采了!异步加上非阻塞IO才有威力

    2019-04-04
    6
    34
  • 天涯煮酒
    合理。

    每个rpc请求都会占用一个线程并产生一个新的DefaultFuture实例,它们的lock&condition是不同的,并没有竞争关系

    这里的lock&condition是用来做异步转同步的,使get()方法不必等待timeout那么久,用得很巧妙
    2019-04-02
    26
  • 10buns
    signal唤醒任意一个线程竞争锁,signalAll唤醒同一个条件变量的所有线程竞争锁。但都只有一个线程获得锁执行。区别只是被唤醒线程的数量。
    所以用signalall可以避免极端情况线程只能等待超时,看了代码也是替代了signal
    2019-04-04
    12
  • 密码123456
    不一定。如果这个类是单例,那就不合理。如果是一个实例对应一个请求,那就合理。
    2019-04-02
    9
  • 约书亚
    我有点不理解为什么这么多说合理的同学,Future这种类不应该经常由于用在闭包中,导致在多线程多上下文中传递嘛?如果我有多个线程都对同一个DefaultFuture实例调用get,而每个被唤醒的线程又不signal其他线程,那不就是只有一个线程最终会被唤醒,其他调用get的线程都是因为超时获取到的结果嘛?
    2019-04-06
    7
  • 杨鹏程baci
    老师好,关于我看到你说改成signalall()是优化了,但是我还是不明白如果用signal()可能会带来什么问题,具体优化体现在哪个方面,感觉从代码上出发,done是一个私有对象,也并不存在多个线程共享的问题,用signal()貌似也是够了的吧?
    2019-06-30
    1
    5
  • 右耳听海
    我看每个请求都会新建一个DefaultFuture,这个按道理应该只有一个线程阻塞,为什么需要signall
    2019-04-28
    1
    5
  • 右耳听海
    in the method of org.apache.dubbo.remoting.exchange.support.DefaultFuture#doReceived, I think we should call done.signalAll() instead of done.signal() ,and it's unnecessary to check done != null because it's always true

    作者回复: 留言这两点有同学都提到了。我表示震撼!

    2019-04-28
    5
  • 木刻
    老师今天提到异步转同步,让我想到这两天看的zookeeper客户端源码,感觉应该也是这个机制,客户端同步模式下发送请求后会执行packet.wait,收到服务端响应后执行packet.notifyAll

    作者回复: 👍

    2019-04-02
    4
  • 代码搬运工
    回复:密码12345同学,如果是单例对象,response岂不是乱套了,每一个请求都对应自己的 response。另外singal()是合理的。因为每一个主线程对应一个子线程,不可能存在一个子线程对应多个请求。
    2019-04-02
    4
  • Binggle
    这是一对一的关系,肯定只需要 signal。每个线程都是相互独立的,lock 和 condition 也是各自独享的。

    作者回复: 一对一的关系用signalall也不是不可以

    2019-04-02
    4
  • Geek_e6f3ec
    老师关于dubbo源码的执行流程有一点疑问。
    以下是源码
    // 调用通过该方法等待结果
    Object get(int timeout){
            long start = System.nanoTime();
            lock.lock();
            try{
                while (!isDone()){
                    done.wait(timeout); // 在这里调用了等待方法后面的代码还能执行吗? 我理解的管程,是在条件变量等待队列中阻塞等待,被唤醒之后也不是马上执行也要去管程入口等待队列,也就是lock.lock处等待获取锁。 老师是这样的吗?
                    long cur = System.nanoTime();
                    if (isDone()||cur-start> timeout){
                        break;
                    }
                }
            }finally {
                lock.unlock();
            }
            return returnFromResponse();

        }

     



    作者回复: 会去获取锁,但是获取锁后,会执行wait后的代码

    2019-05-15
    3
  • 牧名
    DefaultFuture本质上只是一种future实现,所以理论上可以有多个线程同时持有同一个future并调用 get方法,如这时候使用signal()就有可能导致有些线程会请求超时
    ```java
    DefaultFuture future = currentClient.request(inv, timeout);
    for(int i=0; i< 10000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(future.get().toString());
            }
        });
    }

    极客时间版权所有: https://time.geekbang.org/column/article/88487
    2019-05-04
    1
    3
  • 浅夏
    2.7.3版本以及不用lock和signal了
    2019-07-26
    2
  • 拯救地球好累
    ---总结---
    1. 简单地讲,根据调用方是否需要等待结果,可以将程序的调用方式分为同步调用和异步调用
    2. 异步调用:调用方创建子线程并让子线程执行某一方法调用
    3. 异步方法:某一方法中创建了子线程执行逻辑,而让主线程返回

    ---思考题---
    DefaultFuture的某个实例并非单线程访问的,可能会有多个线程访问同一个,因此需要用SignalAll通知全部,避免没有通知到正确的线程(不知道DUBBO中DefaultFuture同一个实例会不会共享)
    2019-07-24
    2
  • 苏格拉底23
    老师您好!

    有一个基本的问题不明白,如果每个request对应一个线程,似乎并没有用到共享的资源,那么为什么要加锁呢?

    作者回复: 这里只是利用管程实现线程的阻塞和唤醒

    2019-06-23
    2
  • ycfHH
    作为一个完全不懂dubbo的新人,我很好奇是什么bug能让signal改成signalAll,因为不管怎么看都感觉signal就已经可以了啊(虽然使用signalall也不错)

    作者回复: 优化而已

    2019-05-06
    2
  • ban
    老师,求指教
    DefaultFuturewhile这个类为什么要加 while(!isDone()) 这个条件,我看代码while里面加了done.await(timeout);是支持超时的,就是说设置5秒超时, if (isDone() || cur-start > timeout){,只要超过没有被signal()唤醒,那5秒就会自动唤醒,这时候就会在if (isDone() || cur-start > timeout){ 被校验通过,从而break,退出。这时候在加个while条件是不是没必要。
    还是说加个while条件是因为时间到点的时候自动唤醒后,Response可能是空,而且时间cur-start > timeout 不超时,所以才有必要进行while再一次判断isDone()是否有值。

    作者回复: while条件是编程范式,可以回去看管程原理,搞工程要多重防护。超时后当然很有可能resp是空的

    2019-04-03
    2
  • QQ怪
    我觉得很合理,因为每个请求都会实例化个DefactFeture,所以每个请求一个lock,明确知道需要唤醒哪个线程应该用asign(),同样这样做法也是为了应对高并发情况下的异步转同步需求吧!不知道对不对?😂
    2019-04-02
    2
收起评论
54
返回
顶部