消息队列高手课
李玥
京东零售技术架构部资深架构师
立即订阅
8426 人已学习
课程目录
已完结 41 讲
0/4登录后,你可以任选4讲全文学习。
课前必读 (2讲)
开篇词 | 优秀的程序员,你的技术栈中不能只有“增删改查”
免费
预习 | 怎样更好地学习这门课?
基础篇 (8讲)
01 | 为什么需要消息队列?
02 | 该如何选择消息队列?
03 | 消息模型:主题和队列有什么区别?
04 | 如何利用事务消息实现分布式事务?
05 | 如何确保消息不会丢失?
06 | 如何处理消费过程中的重复消息?
07 | 消息积压了该如何处理?
08 | 答疑解惑(一) : 网关如何接收服务端的秒杀结果?
进阶篇 (21讲)
09 | 学习开源代码该如何入手?
10 | 如何使用异步设计提升系统性能?
11 | 如何实现高性能的异步网络传输?
12 | 序列化与反序列化:如何通过网络传输结构化的数据?
13 | 传输协议:应用程序之间对话的语言
14 | 内存管理:如何避免内存溢出和频繁的垃圾回收?
加餐 | JMQ的Broker是如何异步处理消息的?
15 | Kafka如何实现高性能IO?
16 | 缓存策略:如何使用缓存来减少磁盘IO?
17 | 如何正确使用锁保护共享数据,协调异步线程?
18 | 如何用硬件同步原语(CAS)替代锁?
19 | 数据压缩:时间换空间的游戏
20 | RocketMQ Producer源码分析:消息生产的实现过程
21 | Kafka Consumer源码分析:消息消费的实现过程
22 | Kafka和RocketMQ的消息复制实现的差异点在哪?
23 | RocketMQ客户端如何在集群中找到正确的节点?
24 | Kafka的协调服务ZooKeeper:实现分布式系统的“瑞士军刀”
25 | RocketMQ与Kafka中如何实现事务?
26 | MQTT协议:如何支持海量的在线IoT设备?
27 | Pulsar的存储计算分离设计:全新的消息队列设计思路
28 | 答疑解惑(二):我的100元哪儿去了?
案例篇 (7讲)
29 | 流计算与消息(一):通过Flink理解流计算的原理
30 | 流计算与消息(二):在流计算中使用Kafka链接计算任务
31 | 动手实现一个简单的RPC框架(一):原理和程序的结构
32 | 动手实现一个简单的RPC框架(二):通信与序列化
33 | 动手实现一个简单的RPC框架(三):客户端
34 | 动手实现一个简单的RPC框架(四):服务端
35 | 答疑解惑(三):主流消息队列都是如何存储消息的?
测试篇 (2讲)
期中测试丨10个消息队列热点问题自测
免费
期末测试 | 消息队列100分试卷等你来挑战!
结束语 (1讲)
结束语 | 程序员如何构建知识体系?
消息队列高手课
登录|注册

17 | 如何正确使用锁保护共享数据,协调异步线程?

李玥 2019-08-31
你好,我是李玥。
在前几天的加餐文章中我讲到,JMQ 为了提升整个流程的处理性能,使用了一个“近乎无锁”的设计,这里面其实隐含着两个信息点。第一个是,在消息队列中,“锁”是一个必须要使用的技术。第二个是,使用锁其实会降低系统的性能。
那么,如何正确使用锁,又需要注意哪些事项呢?今天我们就来聊一聊这个问题。
我们知道,使用异步和并发的设计可以大幅提升程序的性能,但我们为此付出的代价是,程序比原来更加复杂了,多线程在并行执行的时候,带来了很多不确定性。特别是对于一些需要多个线程并发读写的共享数据,如果处理不好,很可能会产出不可预期的结果,这肯定不是我们想要的。
我给你举个例子来说明一下,大家应该都参与过微信群投票吧?比如,群主说:“今晚儿咱们聚餐,能来的都回消息报一下名,顺便统计一下人数。都按我这个格式来报名。”然后,群主发了一条消息:“群主,1 人”。
这时候小六和无双都要报名,过一会儿,他俩几乎同时各发了一条消息,“小六,2 人”“无双,2 人”,每个人发的消息都只统计了群主和他们自己,一共 2 人,而这时候,其实已经有 3 个人报名了,并且,在最后发消息的无双的名单中,小六的报名被覆盖了。
这就是一个非常典型的由于并发读写导致的数据错误。使用锁可以非常有效地解决这个问题。锁的原理是这样的:任何时间都只能有一个线程持有锁,只有持有锁的线程才能访问被锁保护的资源。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《消息队列高手课》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(21)

  • 糖醋🏀
    java7开始io就有try-with-resource。
    可以利用这一个特性,来说实现,自动释放。
    代码如下:

    public class AutoUnlockProxy implements Closeable {

        private Lock lock;

        public AutoUnlockProxy(Lock lock) {
            this.lock = lock;
        }

        @Override
        public void close() throws IOException {
            lock.unlock();
            System.out.println("释放锁");
        }

        public void lock() {
            lock.lock();
        }

        public void tryLock(long time, TimeUnit unit) throws InterruptedException {
            lock.tryLock(time, unit);
        }

        public static void main(String[] args) {

            try (AutoUnlockProxy autoUnlockProxy = new AutoUnlockProxy(new ReentrantLock())) {
                autoUnlockProxy.lock();
                System.out.println("加锁了");
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    作者回复: 👍👍👍

    2019-09-01
    3
    13
  • shenbinzhuo
    既然是消息队列自然要多讲讲消息队列的知识,一下io,一下缓存,一下多线程,讲的很杂,也不深入。
    2019-09-08
    3
  • L!en6o
    加一个锁回调 封装起来 实现 try-with-lock
    2019-08-31
    3
  • 张三
    幸亏学过极客时间的并发编程专栏,看懂了。我觉得并发容器的选择比较复杂。
    2019-08-31
    3
  • humor
    /**
    *业务调用接口
    **/
    public interface Invoker{
    void invoke();
    }

    /**
    *try-with-lock
    **/
    public class RLock{
    private Lock lock = new ReentrantLock();

    public void run(Invoker invoker) {
    lock.lock();
    try{
    invoker.invoke();
    } finally{
    lock.unlock();
    }
    }
    }

    public BizInvoker implements Inoker{
    private RLock rLock = new RLock();
    public void invoke() {
    //需要加锁的业务逻辑
    }

    public static void main(String[] args) {
    rLock.run(new BizInvoker());
    }
    }

    作者回复: 👍👍👍

    2019-09-01
    2
  • Switch
    try with resource实现
    传入参数实现

    https://github.com/Switch-vov/mq-learing/tree/master/src/main/java/com/switchvov/lock
    2019-10-24
    1
  • 文正
    一般情况下,如果业务场景需要调用多个锁,应该将这几把锁归放到一个盒子里面。加锁解锁的条件,在定义锁的时候就规定好。比如,拿到B锁的条件是必须已经拿到A锁。这样可以有效的避免人为的失误。
    2019-11-17
  • 长期规划
    避免死锁
    1。锁最好不要嵌套,如果实在需要嵌套,要按2的顺序
    2。所有锁的地方都按相同的顺序加锁。比如有A,B,C,D四把锁,所有加锁的地方的顺序都是A→B→C→D,或者是其切片,比如线程1使用B→C,线程2使用A→B,线程3使用C→D
    3。解锁顺序最好和加锁顺序相反,这样逻辑清晰,锁不用后一定要释放
    2019-10-08
  • 长期规划
    老师,只要保证所有使用锁的地方都按相同的顺序获取,而且按获取的顺序的反序解锁,应该就不会发生死锁了吧。

    作者回复: 即使是这样也不能完全保证没有死锁。

    2019-10-08
    1
  • 长期规划
    那个读写锁,读锁和写锁之间应该也是互斥的吧

    作者回复: 是的,只有读锁之间是共享的。

    2019-10-08
  • 陈凯
    干货满满👍
    2019-10-08
  • godtrue
    学过宝令老师的并发编程,再看老师这个感觉很轻松,不过毕竟专栏主题所限,宝令老师整个专栏都在讲并发编程更加系统和专业。
    JAVA中实现一个try-with-lock,思路有两个:
    1:注解
    2:代理,静态或动态
    2019-09-24
  • 明日
    请问老师这种在new 对象过程中锁定共享锁的方式是否可行
     代码: https://gist.github.com/imgaoxin/91234a9edbf083b10244221493ce7fb5

    作者回复: 这样做应该是可以的。

    2019-09-03
  • 刘天鹏
    对于golang应该就是这样吧
    func foo(){
      lock.Lock()
      defer lock.Unlock()
      //do something...
    }

    作者回复: 是的。

    2019-09-02
  • 你说的灰

    public void visitShareResWithLock() {
      lock.lock();
      try {
        // 在这里安全的访问共享资源
      } finally {
        lock.unlock();
      }
    }

     lock.lock(); 加锁语句放在 try catch 里面是否可以。为什么很多示例代码都放在外面。

    作者回复: 放到try之后的第一句也是可以的。

    2019-09-02
    1
  • Cast
    老师,请问为什么要按逆序去释放锁呢?按照获取的顺序去释放好像也没什么毛病吧?

    作者回复: 如果释放几把锁的过程中,不再有其它加锁的代码,正序释放也是没问题的。

    逆序释放只是一种习惯,让代码的结构更清晰。

    2019-09-01
  • 游弋云端
    ABBA锁最容易出问题,老师的经验很重要,尽可能避免锁中锁。
    2019-09-01
  • monalisali
    老师,请教一个问题:假设有一个方法在计算报表,但这个计算的线程在执行过程中被意外释放了(并不是抛异常), 此时try catch捕获是捕获不到这种情况的。而从客户端看来,这个计算过程就永远停在那里了,而后台又没能力告诉客户端:“你别等了”。 这种情况应该如果处理呢?

    作者回复: 可以这样解决,每个计算任务都需要一个存储来存放,这个存储可以MySQL、Redis或者ZooKeeper,后端执行计算任务的服务负责更新任务的状态(比如计算中,计算完成,计算失败等),在计算过程中,计算服务定时去更新这个任务的时间戳,如果任务状态是计算中,但时间戳长时间未更新,可以认为是计算服务宕机了。类似于一个心跳机制。

    2019-08-31
    1
  • 树梢的果实
    C语言下,通过宏很容易实现try-with-lock。
    如果两个线程中获取mutex的顺序不一致,可以通过增加第三个mutex来避免死锁。
    既然我们做异步、并行,磁盘读写也可以这么做啊,加一个queue,所有读写操作请求都放到queue中,在单独的线程中完成IO操作并通过callback或另一个queue返回结果。不知服务器上这么做有什么不妥?

    作者回复: 这样做是可以的,其实你用的这个阻塞队列它就是用锁来实现的。

    2019-08-31
  • 许童童
    老师这篇文章的分享对我这样的非后端程序员很友好,感谢老师的分享。
    2019-08-31
收起评论
21
返回
顶部