场景
首先拟定一个场景:
张麻子同时付款购买十件商品,则有十个线程同时验证余额是否充足并扣款。
张麻子初始资金为2000元,每件商品为200元,如果全部购买成功则张麻子应该还剩0元。
数据库初始状态为:
数据库初始状态
无锁方式
无锁方式
通过上述代码可以看出,当多个线程并发修改一条数据时是有问题的,当第一个线程查询出余额为2000元,还正在判断余额是否足够时其余的19个线程也将2000元读入了内存,然后同时将1800修改到数据库。最后导致数据不一致。
悲观锁
悲观锁实现方式
首先需要开启事务,在查询时使用"for update"对该数据施加行级锁,被施加行级锁的数据只允许持有锁的线程修改,其他线程都会被阻塞,需要等待锁释放后才能修改。而现在十个线程都在查询时施加"for update"所以只能等待第一个线程锁释放后,后边的线程才可以再次查询并加锁。
乐观锁
首先要在表里增加版本号字段,在修改之前先执行查询操作,在修改时的过滤条件中指定修改的版本号,并将版本号加一,若在此期间数据已被其他线程修改,则修改失败。根据返回的执行成功数量判断是否执行成功,若失败则重新查询并修改,也可以直接返回,建议重试的次数在三次以上。
总结
悲观锁适用于多写少读的场景,在竞争激烈的场景下避免了反复修改失败反复重试的而造成的资源浪费,但会影响到并发的响应速度,因为加锁和释放锁都需要消耗额外的资源。