02 | 代码加锁:不要让“锁”事成为烦心事
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
本文深入探讨了多线程编程中使用锁解决线程安全问题的重要性和注意事项。作者通过具体案例和代码分析,指出了常见的误区和错误做法。首先,作者分享了一个有趣的案例,展示了在操作多个字段时出现的线程安全问题,以及为方法加锁未能解决问题的原因。其次,作者提到了另一种常见错误,即没有理清锁和被保护对象的层面关系,导致无效的加锁操作。最后,作者强调了加锁时需要考虑锁的粒度和场景问题,避免滥用synchronized关键字,以及在需要保护共享资源时降低锁的粒度。此外,作者还提到了区分读写场景以及资源的访问冲突,考虑使用悲观方式的锁还是乐观方式的锁的建议。总的来说,本文通过具体案例和代码分析,深入浅出地介绍了多线程编程中使用锁解决线程安全问题的重要性和注意事项。文章内容丰富,对于需要解决多线程编程中的线程安全问题的读者具有很高的参考价值。
《Java 业务开发常见错误 100 例》,新⼈⾸单¥59
全部留言(59)
- 最新
- 精选
- Darren置顶思考与讨论: volatile的问题:可见性问题和禁止指令重排序优化。 可见性问题:本质上是cpu缓存失效,必须从主内存读取数据; 禁止指令重排序优化:x86处理器仅下,只实现了volatile的读写内存屏障,也就是store load,也就是写读,本质上也就是读写可见性,happen-before原则。 实现原理是通过寄存器esp实现的。 当然也不会退出循环,因为cpu缓存到主内存的同步不是实时的。 锁释放和重复执行问题:锁建议使用synchronized,在JDK1.6后,synchronized与Lock性能上差距很小了(优化了很多,自旋锁,自适应自旋锁、偏向锁,轻量级锁等),synchronized也不用程序获取和释放锁,同步代码块是通过monitorenter monitorexit实现的,同步方法是方法头中有ACC_SYNCHRONIZED标志;在分布式场景下,可以考虑etcd,etcd支持锁的自动续期等; 重复执行:首先在锁的使用场景下做好处理,尽量避免重复执行,但业务层面一定要做好幂等。
作者回复: 👍🏻
2020-03-1090 - Seven.Lin澤耿1.加群解锁没有配对可以用一些代码质量工具协助排插,如Sonar,集成到ide和代码仓库,在编码阶段发现,加上超时自动释放,避免长期占有锁 2.锁超时自动释放导致重复执行的话,可以用锁续期,如redisson的watchdog;或者保证业务的幂等性,重复执行也没问题。
作者回复: 这个回答太赞了!
2020-03-10361 - 睿睿睿睿睿睿、老师我有个意见代码能否不要大量使用Lambda表达式,并不是每个读者都是老司机
作者回复: 其实Java8出来已经挺久了,使用Lambda和Stream可以显著改善代码可读性,确保代码简洁性,因此专栏是大量使用Java8的一些新特性的。给你几个建议: 1、可以进一步订阅极客时间专门的学习java的专栏系统学习Lambda语法,比如https://time.geekbang.org/course/detail/181-107395,然后自己对着练习一下 2、买一本《Java实战第二版》系统学习Java8的方方面面 3、关注一下本专栏的加餐,之后我们会通过加餐介绍下Java8 4、遇到实在看不懂的代码,下载源码后,在IDEA中点击lambda或stream API的地方,停留一下,左侧可以看到有提示 replace stream API with loop或replace lambda with anonymous class选项,翻译为非stream和lambda的语法,帮助你理解
2020-03-111051 - 黄海峰超时自动释放锁后怎么避免重复逻辑好难,面试曾被卡,求解。。。
作者回复: 有两个方面:1. 避免超时,单独开一个线程给锁延长有效期。比如设置锁有效期30s,有个线程每隔10s重新设置下锁的有效期。 2. 避免重复,业务上增加一个标记是否被处理的字段。或者开一张新表,保存已经处理过的流水号。
2020-03-10739 - 编程界的小学生1.不能退出。必须加volatile,因为volatile保证了可见性。改完后会强制让工作内存失效。去主存拿。如果不加volatile的话那么在while true里面添加输出语句也是OK的。因为println源码加锁了,sync会让当前线程的工作内存失效。 解释的对吗?献丑了。
作者回复: 嗯必须加volatile或者使用AtomicBoolean/AtomicReference等也行,后者相比volatile除了确保可见性还提供了CAS方法保证原子性
2020-03-09425 - 汤杰对着代码看锁过期蒙了半天,还以为trylock的时间不是等待锁的时间,以为我一直理解的是错误的。最好加上特定的条件。本地锁哪有锁过期呢。原来有些分布式锁为了防止调用方挂了不释放锁加了超时。看到有说用客户端续期的,业务保证的,业务的确一定要保证的,用分布式锁可以解决业务数据库幂等在高并发冲突强烈下性能降低。
作者回复: 抱歉,因为本文删除了原来有的分布式锁的例子,所以最后总结这边的描述谈到的『锁自动超时释放问题』有点唐突,我们改一下。你理解的没错,锁过期是指分布式锁的过期,本地锁是只有等待锁超时
2020-03-1113 - insight看老师使用Lambda表达式感觉学到了非常多,非常支持老师这样做,毕竟程序员就是要不断走出舒适区,学习新东西的。就是老师的Lambda加餐能不能早一点来,对照起来看的更舒服一些
作者回复: 应该快了,大概是下周
2020-03-1112 - 郑思雨一、加锁和释放没有配对: lock 与 unlock 通常结对使用,使用时,一般将unlock放在finally代码块中。但是释放锁时最好增加判断: if (lock.isHeldByCurrentThread()) lock.unlock(); 这样避免锁持有超时后释放引发IllegalMonitorStateException异常。 如果怕忘记释放锁,可以将锁封装成一个代理模式,如下: public class AutoUnlockProxy implements Closeable { private Lock lock; public AutoUnlockProxy(Lock lock){ this.lock = lock; } public void lock(){ lock.lock(); } public boolean tryLock(){ return lock.tryLock(); } @Override public void close() throws IOException { lock.unlock(); } } 使用时,通过try-with-resource 的方式使用,可以达到自动释放锁的目的: try(AutoUnlockProxy proxy = new AutoUnlockProxy(new ReentrantLock())){ proxy.lock(); }catch (Exception e){ e.printStackTrace(); } 二、锁自动释放导致的重复逻辑执行(补充的细节点) 1、代码层面:对请求进行验重; 2、数据库层面:如果有插入操作,建议设置唯一索引,在数据库层面能增加一层安全保障;
作者回复: 赞
2020-08-07211 - 看不到de颜色关于锁过期问题。以前做redis分布式锁的时候一直在思考这个问题。当时觉得就是尽量让锁过期时间比程序执行之间略长一些,以保证加锁区域代码能尽量执行完成。看到老师给其他同学评论说可以用另外一个线程去不断重置锁时间,这里有我理解是针对像redis这种利用setnx实现的分布式锁可以这么解决。那还有其他场景吗?
作者回复: 就是锁续期解决 可以看一下redisson实现
2020-03-2536 - pedrovolatile 老生长谈的问题了,关于锁过期,如果开启一个线程续期,但是有最大重试次数,比如 5 次,那么 5 次以后如何保证其它线程拿到锁而不会重复执行业务了?
作者回复: 可以无限续期,比如redisson的RedissonLock,锁续期是每次续一段时间,比如30秒,然后10秒执行一次续期,虽然是无限次续期,即使客户端崩溃了也没关系无法自动续期后自然会超时
2020-03-1036