springboot与锁

1.应用场景

开发过程中经常遇到多线程更新数据实体,例如库存管理(入库与出库,出库与出库)等。

多线程更新数据实体会造成数据覆盖,造成bug.

2.场景模拟

batch(){
    for(15){
        new Thread(()->{test()}).start()
    }
}

test(){
    entity = findById(10L)
    entity.setNum(entity.getNum()-1)
    save(entity);
}

初始化库存100,运行结束库存90。

3.解决方案

可以从两方面解决:应用层面和数据库层面。应用层面使用synchronize或ReentrantLock,数据库层面for update 或者 乐观锁。

4.锁

synchronize 修饰 test() 方法。

ReentrantLock 可重入锁,test() 使用的经典范式:

lock.lock()
try{
    //业务代码
}finally{
    lock.unlock()
}

synchronize 优化后性能和Lock 差不多;不过ReentrantLock更灵活,可以用非阻塞方式获取锁,可以响应中断,可以设置阻塞时间。ReentrantLock 可以使公平锁或非公平锁,synchronize只能是非公平锁。ReentrantLock是jdk提供的,synchronize是jvm提供的。

事务和锁发生的异常

如果test方法启用@Transational 可能会发生异常,因为Transational真正启动时是业务代码第一条sql语句,提交事务是在执行方法体后。执行过程

lock-->transational start -->unlock -->transaciontal commit ,无法保证事务的原子性。

可以改变执行过程 lock -->transational start -->transational end -->unlock

解决方案是:业务代码单独写一个方法,启动事务。

5.sql

(1)悲观锁

select * from table where id =#{id} for update

查询数据加行锁,更新完成,释放锁。注意id必须是索引,否则行锁会升级为表锁。

(2)乐观锁

在实体类和表加字段version;在更新之前比较版本号,如果版本号不同说明已经有更新了,更新失败,重新读取实体数据进行更新。

update client set num =#{num} ,version = version + 1 where id = #{id} and version = #{version}

你可能感兴趣的:(mybatis,spring,boot)