一、问题描述
最近在项目开发中遇到一个场景,一个共享资源可能在同一时间被多个线程访问并更新,此时一般有两种解决方案。第一种就是在相应的方法上加synchronized关键字,我们称之为悲观锁,即锁住共享资源,等待当前线程访问并更新完毕,其他线程才可以操作共享资源。第二种是不给资源加锁,即无锁,线程可以直接去访问并更新资源,只是在更新资源的同时判断该共享资源有没有被更新,如果没有被更新,则执行更新操作,反之,提示异常或者自动重试。
二、举例说明
java锁主要是对比较敏感的数据进行设置,防止出现脏数据的现象,比如我们有一张user表,对应的实体类
import lombok.Data
@Data
@TableName("user")
public class User{
//主键
Long id;
//用户名
String username;
用户密码
String password;
//用户余额
Integer balance;
}
有一个方法可以更新用户余额,
@Autowired
private final UserDao userDao;
/**
* @param userId 用户id
* @param consume 用户消费的金额
*/
public Boolean update(Long userId, Integer consume){
//通过用户id找到用户
User user = userService.findById(userId);
//获得更新后的余额(1)
int updateBalance = user.getBalance() - consume;
//保存更新的余额
user.setBalance(balance);
//调用userDao更新数据库
userDao.update(user);
}
一般业务里面我们可以通过这样的方式实现对用户字段进行更新,但是可能同时有其他线程要更新这个字段值,那我们
在进行(1)这一步的时候,拿到了用户的余额,此时资源还没有被更新,当执行到数据库sql语句的时候,update user set balance = #{updateBalance}在此操作进行之前用户余额被其他线程修改,那么我们忽略了这个线程的修改操作(可能是平台给予的奖励等等)。那么怎么解决呢?
可以使用乐观锁的原理,在mapper种的update语句可以调整为
<update id = update>
update user set user.balance = #{updateBalance}
<!-- 加上一个where判断 getBalance是方法传进来的用户余额-->
where user.balance = #{getBalance}
</update>
这样就可以解决这个问题,如果getBalance != user.balance则不执行更新操作,并提示用户异常,重新尝试