17 | 如何正确使用锁保护共享数据,协调异步线程?
李玥
该思维导图由 AI 生成,仅供参考
你好,我是李玥。
在前几天的加餐文章中我讲到,JMQ 为了提升整个流程的处理性能,使用了一个“近乎无锁”的设计,这里面其实隐含着两个信息点。第一个是,在消息队列中,“锁”是一个必须要使用的技术。第二个是,使用锁其实会降低系统的性能。
那么,如何正确使用锁,又需要注意哪些事项呢?今天我们就来聊一聊这个问题。
我们知道,使用异步和并发的设计可以大幅提升程序的性能,但我们为此付出的代价是,程序比原来更加复杂了,多线程在并行执行的时候,带来了很多不确定性。特别是对于一些需要多个线程并发读写的共享数据,如果处理不好,很可能会产出不可预期的结果,这肯定不是我们想要的。
我给你举个例子来说明一下,大家应该都参与过微信群投票吧?比如,群主说:“今晚儿咱们聚餐,能来的都回消息报一下名,顺便统计一下人数。都按我这个格式来报名。”然后,群主发了一条消息:“群主,1 人”。
这时候小六和无双都要报名,过一会儿,他俩几乎同时各发了一条消息,“小六,2 人”“无双,2 人”,每个人发的消息都只统计了群主和他们自己,一共 2 人,而这时候,其实已经有 3 个人报名了,并且,在最后发消息的无双的名单中,小六的报名被覆盖了。
这就是一个非常典型的由于并发读写导致的数据错误。使用锁可以非常有效地解决这个问题。锁的原理是这样的:任何时间都只能有一个线程持有锁,只有持有锁的线程才能访问被锁保护的资源。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
锁的正确使用对于保护共享数据和协调异步线程至关重要。本文通过实际例子和技术原则详细介绍了如何正确使用锁来保护共享数据和协调异步线程。首先,文章指出了使用锁的必要性,以微信群报名的例子说明了并发读写可能导致的数据错误。然后,强调了避免滥用锁的重要性,提出了避免使用锁的原则。接着,文章介绍了锁的用法,包括在Java语言中使用Lock和synchronized关键字的例子,并强调了释放锁的重要性以避免死锁问题。此外,文章还提到了死锁的可能原因和如何避免死锁的建议。另外,文章还介绍了读写锁的使用,以兼顾性能和安全性。总的来说,本文通过实际案例和技术原则,详细介绍了如何正确使用锁来保护共享数据和协调异步线程,对于需要处理并发和异步操作的开发人员具有重要的参考价值。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《消息队列高手课》,新⼈⾸单¥59
《消息队列高手课》,新⼈⾸单¥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(); } } }
作者回复: 👍👍👍
2019-09-01474 - 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-0113 - Cast老师,请问为什么要按逆序去释放锁呢?按照获取的顺序去释放好像也没什么毛病吧?
作者回复: 如果释放几把锁的过程中,不再有其它加锁的代码,正序释放也是没问题的。 逆序释放只是一种习惯,让代码的结构更清晰。
2019-09-01310 - 喜欢地球的阿培同学老师突然想到一个问题,假设现在100个线程,一个线程正在运行,99个线程正在阻塞(等待锁释放),那么会导致CPU上下文频繁切换吗?
作者回复: 不会。
2020-04-176 - 道阻塞队列能够解决大部分并发访问的问题,Golang对他就提供了语言层面的支持,为何现实中用的不多哩?
作者回复: 还是要看具体的使用场景。
2020-02-03 - 长期规划老师,只要保证所有使用锁的地方都按相同的顺序获取,而且按获取的顺序的反序解锁,应该就不会发生死锁了吧。
作者回复: 即使是这样也不能完全保证没有死锁。
2019-10-083 - 长期规划那个读写锁,读锁和写锁之间应该也是互斥的吧
作者回复: 是的,只有读锁之间是共享的。
2019-10-08 - 明日请问老师这种在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-022
收起评论