MySQL数据库实现分布式锁 | 乐观锁 | 比较并交换(CAS)

概述

在只有一台机器的情况下,我们也会碰到类似的情况,比如在多个线程需要访问某个共享资源的时候,我们就可以采用加锁的形式。在Java中,一个简单的办法就是使用synchronized关键字来对方法或者变量加锁。
但是,这种加锁方式在进程之间的共享就显得力不从心了起来。为了解决这样的问题,我们必须引入分布式锁。分布式锁一般会用于互斥资源的访问。
在这里,我们将使用MySQL数据库来实现分布式锁。当然,分布式锁还有其他的实现方式,如通过rediszookeeper等,这些方式可以很方便的在网上找到实现方案。从性能上来说,使用缓存(Redis) > Zookeeper > MySQL数据库
Redis固然是可以实现分布式锁,但是在QPS不高的情况下,使用这样的分布式锁会带来复杂性。使用MySQL数据库能够快速开发。很多时候,先进的方案并不会比老旧的方案带来更多的收益,那这种时候使用简单的方案也是一个很好的方案。

基于乐观锁(CAS)的方案

顾名思义,乐观锁在大多数的情况下不会发生冲突,使用乐观锁为了保证其在不冲突情况下的性能。实际上,乐观锁并不是锁,所以省下了加锁、释放锁带来的时间和资源的消耗。同时,无锁机制自然也就避免了死锁的产生。
乐观锁实际上是一种思想:比较并交换(Compare and swap, CAS),简单来说就是当数据是原来的数据时,我才会进行交换。如下一段Java代码就能够解释这个操作。

public boolean cas(int addr, int oldValue, int newValue) {
    if (addr != oldValue) return false;
    addr = newValue;
    return true;
}

其中,addr指的是需要进行修改的数据,oldValue是执行修改之前的数据,newValue指的是希望修改成的数据。当我们有2个进程共同执行修改操作时,只会有一个能够生效,同时返回true,另外一个将会修改失败,返回false。
需要注意,一次cas操作必须是原子的,否则无法正常使用。

ABA问题

该问题可以简化为以下的时序图

时序目标的值进程1其他进程
10读取0-
20-读取0
31-修改为1
40-修改为0
51修改为1-

可以注意到其他进程将数据修改为1后又修改回0,那么进程1会以为并没有进行过修改,那么就会执行后续的操作,这可能会引起一些问题。
为了解决这样的问题,可以考虑在字段上面加入一个版本号,每次修改需要比对版本号,同时修改成功了版本号需要+1。当然也可以使用时间戳来实现同样的功能。

具体实现

数据库可以这样建表test_table

NameTypeNot nullComment
idintid,唯一
valueint需要修改的值
update_timeint更新时间

在使用乐观锁时,需要保证数据库中有数据,随后才能更新数据。

UPDATE
    test_table
SET
    value = #{ value },
    update_time = #{ newUpdateTime }
WHERE
    id = #{ id }
    AND update_time = #{ oldUpdateTime }

由于mysql中的update会返回更新的行数,所以只需要判断返回的值是否是1即可。
如果为0,需要重新获取一次数据库中的数据,并重新进行一次更新。这里要注意循环次数需要做一个限制,否则可能会导致死循环。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值