乐观锁和悲观锁

时间:2022-09-21 09:30:33

一。定义:

乐观锁:就是在操作时很乐观,这数据只有我在用,我先尽管用,最后发现别人修改了数据则回滚。(应用层加锁)

悲观锁:在操作时很悲观,认为其他人同时更新,因此我就先将其先锁住,让别人用不了,我操作完成后再释放掉。(数据库层加锁 for update)

二。实现方式

悲观锁:法1.对代码块加锁(如Java的synchronized关键字);法2:对数据加锁(如MySQL中的排它锁  for update,其他的事务是可以读取的。但是不能写入或者更新。 

  顺带一提的是,当选中某一个行的时候,如果是通过主键id选中的。那么这个时候是行级锁。
  其他的行还是可以直接insert 或者update的。如果是通过其他的方式选中行,或者选中的条件不明确包含主键。这个时候会锁表。其他的事务对该表的任意一行记录都无法进行插入或者更新操作。只能读取)。

乐观锁:法1:版本号机制;法2:CAS机制

三。优缺点

1.功能限制

2.并发冲突的概率

出现并发冲突的概率小时,乐观锁更有优势,因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源。

  (乐观锁本身是不加锁的,只是在更新时判断一下数据是否被其他线程更新了)

  注意:乐观锁不能解决脏读的问题。

出现并发冲突的概率大时,悲观锁更有优势,因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。

四。举例

考虑这样一种场景:游戏系统需要更新玩家的金币数,更新后的金币数依赖于当前状态(如金币数、等级等),因此更新前需要先查询玩家当前状态。

下面的实现方式,没有进行任何线程安全方面的保护。如果有其他线程在query和update之间更新了玩家的信息,会导致玩家金币数的不准确。

@Transactional //不加锁
public void updateCoins(Integer playerId){
    //根据player_id查询玩家信息
    Player player = query("select coins, level from player where player_id = {0}", playerId);
    //根据玩家当前信息及其他信息,计算新的金币数
    Long newCoins = ……;
    //更新金币数
    update("update player set coins = {0} where player_id = {1}", newCoins, playerId);
}

为了避免这个问题,悲观锁通过加锁解决这个问题,代码如下所示。在查询玩家信息时,使用select …… for update进行查询;该查询语句会为该玩家数据加上排它锁,直到事务提交或回滚时才会释放排它锁;在此期间,如果其他线程试图更新该玩家信息或者执行select for update,会被阻塞。

@Transactional
public void updateCoins(Integer playerId){
    //根据player_id查询玩家信息(加排它锁)
    Player player = queryForUpdate("select coins, level from player where player_id = {0} for update", playerId);//悲观锁  排他锁
    //根据玩家当前信息及其他信息,计算新的金币数
    Long newCoins = ……;
    //更新金币数
    update("update player set coins = {0} where player_id = {1}", newCoins, playerId);
}

乐观锁:版本号机制则是另一种思路,它为玩家信息增加一个字段:version。在初次查询玩家信息时,同时查询出version信息;在执行update操作时,校验version是否发生了变化,如果version变化,则不进行更新。

@Transactional
public void updateCoins(Integer playerId){
    //根据player_id查询玩家信息,包含version信息
    Player player = query("select coins, level, version from player where player_id = {0}", playerId);
    //根据玩家当前信息及其他信息,计算新的金币数
    Long newCoins = ……;
    //更新金币数,条件中增加对version的校验
    update("update player set coins = {0} where player_id = {1} and version = {2}", newCoins, playerId, player.version);//乐观锁 版本号机制
}