1.问题背景

在实际项目中,对数据的更新操作是避免不了的。保持数据的一致性是非常重要的。 之前考虑到是否可以通过比较update的时间,或者单独保存一个时间戳字段来进行比较,但需要手动更新,比较麻烦且容易出错。
  比如在一个简单的交易订单系统中,对于同一笔订单的更新动作是很频繁的,比如更新订单状态的同时并发去更新订单其他字段信息,就会导致数据不一致。
  可以使用乐观锁来解决这种问题,可以有效的提高程序的吞吐量。

2.解决方案

通过对数据添加乐观锁来解决这个问题,乐观锁(Optimistic Locking)是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突进行检测。如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。

2.1 通过添加版本号实现乐观锁的实例

  • 存在两个线程 A 和 B,分别从数据库读取数据。执行后,线程 A 和 线程 B 的 version 均等于 1。如下图:

Java后端并发 java并发处理数据_java并发

  • 线程 A 处理完业务,提交数据。此时,数据库中该记录的 version 为 2。如下图:

Java后端并发 java并发处理数据_Java后端并发_02

  • (3)线程 B 也处理完业务了,提交数据。此时,数据库中的 version 已经等于 2,而线程的 version 还是 1。程序给出错误信息,不允许线程 B 操作数据。如下图:

Java后端并发 java并发处理数据_Java后端并发_03

2.2 乐观锁使用总结

乐观锁机制采取了更加宽松的加锁机制。乐观锁是相对悲观锁而言,也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。乐观锁的实现:

  • CAS 实现:Java 中 java.util.concurrent.atomic 包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
  • 版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会+1。当线程A要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。