消息队列高手课
李玥
美团高级技术专家
51348 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 42 讲
进阶篇 (21讲)
消息队列高手课
15
15
1.0x
00:00/00:00
登录|注册

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

你好,我是李玥。
在前几天的加餐文章中我讲到,JMQ 为了提升整个流程的处理性能,使用了一个“近乎无锁”的设计,这里面其实隐含着两个信息点。第一个是,在消息队列中,“锁”是一个必须要使用的技术。第二个是,使用锁其实会降低系统的性能。
那么,如何正确使用锁,又需要注意哪些事项呢?今天我们就来聊一聊这个问题。
我们知道,使用异步和并发的设计可以大幅提升程序的性能,但我们为此付出的代价是,程序比原来更加复杂了,多线程在并行执行的时候,带来了很多不确定性。特别是对于一些需要多个线程并发读写的共享数据,如果处理不好,很可能会产出不可预期的结果,这肯定不是我们想要的。
我给你举个例子来说明一下,大家应该都参与过微信群投票吧?比如,群主说:“今晚儿咱们聚餐,能来的都回消息报一下名,顺便统计一下人数。都按我这个格式来报名。”然后,群主发了一条消息:“群主,1 人”。
这时候小六和无双都要报名,过一会儿,他俩几乎同时各发了一条消息,“小六,2 人”“无双,2 人”,每个人发的消息都只统计了群主和他们自己,一共 2 人,而这时候,其实已经有 3 个人报名了,并且,在最后发消息的无双的名单中,小六的报名被覆盖了。
这就是一个非常典型的由于并发读写导致的数据错误。使用锁可以非常有效地解决这个问题。锁的原理是这样的:任何时间都只能有一个线程持有锁,只有持有锁的线程才能访问被锁保护的资源。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《消息队列高手课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(36)

  • 最新
  • 精选
  • 糖醋🏀
    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(); } } }

    作者回复: 👍👍👍

    4
    74
  • 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()); } }

    作者回复: 👍👍👍

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

    作者回复: 如果释放几把锁的过程中,不再有其它加锁的代码,正序释放也是没问题的。 逆序释放只是一种习惯,让代码的结构更清晰。

    3
    10
  • 喜欢地球的阿培同学
    老师突然想到一个问题,假设现在100个线程,一个线程正在运行,99个线程正在阻塞(等待锁释放),那么会导致CPU上下文频繁切换吗?

    作者回复: 不会。

    6
  • 阻塞队列能够解决大部分并发访问的问题,Golang对他就提供了语言层面的支持,为何现实中用的不多哩?

    作者回复: 还是要看具体的使用场景。

  • 长期规划
    老师,只要保证所有使用锁的地方都按相同的顺序获取,而且按获取的顺序的反序解锁,应该就不会发生死锁了吧。

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

    3
  • 长期规划
    那个读写锁,读锁和写锁之间应该也是互斥的吧

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

  • 明日
    请问老师这种在new 对象过程中锁定共享锁的方式是否可行 代码: https://gist.github.com/imgaoxin/91234a9edbf083b10244221493ce7fb5

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

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

    作者回复: 是的。

  • 你说的灰
    public void visitShareResWithLock() { lock.lock(); try { // 在这里安全的访问共享资源 } finally { lock.unlock(); } } lock.lock(); 加锁语句放在 try catch 里面是否可以。为什么很多示例代码都放在外面。

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

    2
收起评论
显示
设置
留言
36
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部