防止并发修改 之 离线乐观锁代码示例(菜鸟必读)
背景
小明和小强同时签出了源代码,如果小强先提交,那么提交成功是合理的,接着小明提交了修改,这时源代码服务器就会告诉小明有人在他读取之后做了修改,问他如何处理,源代码服务器会让小明把修改合并后再提交。这就是乐观锁策略,当然源代码服务也可以配置为悲观锁以避免并行修改。
合理的规避并发修改是企业应用中不能回避的问题,但现实场景是,很多团队都回避这个问题。今天我介绍一下如何使用离线乐观锁处理并发修改。
相关文章:再谈在线悲观锁、离线悲观锁、在线乐观锁和离线乐观锁。
思路
CAS:Compare And Swap,只有当要修改的值在我读取后没有被修改,才会被交换(修改)。
CAS是多线程领域的术语,比如:无锁的环形队列就是基于这个实现的。因为CAS的思想和乐观锁的思想一致,我就借用一下。
看一下离线乐观锁的应用场景:
上图包含了如下信息:
一、读取线程A1和修改线程A2是不同的线程,因此才叫离线。例如:表单的读取数据和修改。
二:CAS的Compare比较的是版本号,Swap的是整条记录。例如:EntityFramework允许你指定哪些属性是版本属性。
代码示例
设置版本字段
CAS
1 /// <summary> 2 /// 执行修改。 3 /// </summary> 4 public void Handle(TCommand command) 5 { 6 var unitOfWork = ServiceLocator.Current.GetInstance<TUnitOfWork>(); 7 8 unitOfWork 9 .GetRepository<TRepository>() 10 .MarkAsModified(command.Aggregate); 11 12 unitOfWork.Commit(); 13 }
运行效果
注意事项
一、示例中我直接将读取线程读取的数据,离线修改后传递个修改线程了,这样两个线程只有一次读取逻辑,这保证了CAS中Compare的正确性。有些场景你可能需要在修改线程中也进行一次读取,然后将UI层修改的数据合并过来,这种情况就要注意了,必须要手工指定Compare操作使用第一个线程读取的版本号,否则会使用第二次读取的版本号。
二、乐观离线锁只适合重来成本很低的场景,否则用户编辑了两个小时,你告诉他出现并发问题了,他会疯的。这种成本很高的操作适合“离线悲观锁”。
备注
我就是一个行动跟不上思维的人,因为懒惰,我没有全面的在项目中采用乐观锁,下一个项目一定全面实施。