兜兜转转,磕磕绊绊了很久,还未来得及想明白自己要什么,就错过了很多。等等,错了,重来一遍。从大学开始,就很喜欢观察身边的一些小事,思考一些小问题,写一些对生活的小感悟,导致现在提笔就开始聊人生谈理想,随着年龄的增长,以及阅历的不断丰富,经历了一些社会的小考验,逐渐褪去了乌托邦式的理想主义.............
今天我想聊的是和朋友一起深入探讨过的一个问题,如何正确的使用乐观锁机制解决多线程下并发问题。
正常case:
第一次,事件A触发执行 更新数据库为status=INIT
第二次,事件B出发执行 更新数据库为name=小明
第三次,事件C触发执行 更新数据库为status=FINISHED
代码逻辑如下:
先定义方法:
执行结果为:
此时我们看到没有任何问题,达到了我们想要的结果。但是在推到测试环境时,出现了一个问题,最终结果没有被更新为FINISHED。
排查日志发现更新FINISHED代码已经执行,但是没有生效,原因是因为调用方在异常情况下会再次以并发方式执行回调,模拟伪代码如下:
执行结果:
解决方案一:悲观锁
对queryById加上排他锁for update使线程同步,但是结果依旧如此,并未解决。因为此时加上事务,在更新FINISHED时,afterFinished方法如果抛出异常,整体业务状态回滚,不符合业务场景,此方法排除。
解决方案二:乐观锁
允许多线程并发乱序执行,在执行B事件时,加上条件判断,如果是非FINISHED则不执行B事件。结果依旧未解决。因为B事件执行时执行了queryByIdForUpdate,B和C事件同时执行,queryByIdForUpdate查询出的map中状态可能是在事件C执行完FINISHED之前的INIT,此时C先执行完毕,状态变更为FINISHED,B开始执行,更新状态为INIT,由于两次forUpdate并非是原子操作,因此,此方式失败。
解决方案二:乐观锁+代码执行顺序修改
允许多线程并发乱序执行,将updateB修改为只修改updateName,并发乱序问题解决。无论并发任何顺序,都只执行更改不同事件下的状态,B事件执行的更新name操作和顺序无关,不影响执行结果。
当你觉得你什么都不知道的时候,其实你已经知道了很多,我们下次见。
关注我,回复”资料“领取更多学习资料,包括阿里等面试题库