并发控制(乐观锁、悲观锁)


版权声明:本文由 Hov 所有,发布于 http://chenhy.com ,转载请注明出处。



0 前言

乐观锁、悲观锁是人们定义出来的概念,可认为是一种思想。也就是说其不依赖于某个特定的数据库,不仅仅是关系型数据库系统有乐观锁、悲观锁,像 memcache 、 Hibernate 等也有类似的概念。乐观锁、悲观锁是用来保证数据并发安全的方法。


1 悲观锁

1.1 简介

悲观锁并发控制方案,在各种情况下都上锁。上锁之后,就只有一个线程可以操作这一数据。

1.2 实例

牙膏库存为 100 件,用户 A (线程 A )和用户 B (线程 B )同时购买一件牙膏。

  1. 线程 A 读取牙膏库存( 100 件,并对数据加锁),此时线程 B 无法读取牙膏库存数据;
  2. 线程 A 将牙膏库存减 1 ,库存为 100-1=99 件;
  3. 线程 A 将数据更新至数据库,数据库牙膏库存为 99 件,数据解锁;
  4. 因为数据解锁了,线程 B 可以读取牙膏库存数据( 99 件,并对数据加锁);
  5. 线程 B 将牙膏库存减 1 ,库存为 99-1=98 件;
  6. 线程 B 将数据更新至数据库,数据库中牙膏库存变为 98 件,数据解锁。

注:执行顺序为绿色 → 蓝色 → 红色

1.3 优缺点

优点:

  1. 操作方便,直接加锁;
  2. 对程序透明,无需额外操作。

缺点:

  1. 并发能力低,同一时间只能有一个线程操作数据。


2 乐观锁

2.1 简介

乐观锁通俗的理解就是,数据有一个版本号,第一次读的时候将获取数据的版本号;当需要对数据进行更新时,需要检查数据库中的版本号与第一次获取的版本号是否一致。如果一致则更新数据,否则不更新。也就是说,要保证数据在中间没被修改过。

2.2 实例

牙膏库存为 100 件,用户 A (线程 A )和用户 B (线程 B )同时购买一件牙膏。

  1. 线程 A 读取牙膏库存( 100 件,版本号为 1 ,不加锁),线程 B 读取牙膏库存( 100 件,版本号为 1 ,不加锁);
  2. 线程 A 将库存减 1 , 100-1=99 件;线程 B 将库存减 1 , 100-1=99 件(注意还没更新到牙膏库存);
  3. 因为线程 A 、线程 B 总有一个线程必先执行,假设是线程 A 。线程 A 将库存减 1 后(库存为 100-1=99 件),通过本地版本号与数据库版本号比对(都为 1 ),因为版本号一致,所以更新牙膏库存(牙膏库存变为 99 件,版本号变为 2 ),线程 A 执行完毕;
  4. 线程 B 将库存减 1 后(库存为 100-1=99 件),通过本地版本号(为 1 )与数据库版本号(为 2 )比对,因为版本号不一致,所以不更新牙膏库存,线程 B 重新读取牙膏库存(库存为 99 ,版本号为 2 ),重复执行。
  5. 线程 B 将库存减 1 后(库存为 99-1=98 件),通过本地版本号(为 2 )与数据库版本号(为 2 )对比,因为版本号一致,所以更新牙膏库存(牙膏库存变为 98 件,版本号变为 3 ),线程 B 执行完毕。

注:执行顺序为绿色 → 蓝色 → 红色

2.3 优缺点

优点:

  1. 并发能力好;
  2. 不给数据加锁。

缺点:

  1. 繁琐,每次更新都要比较版本号;
  2. 可能需要重复加载数据。


 
comments powered by Disqus