外包公司入职第3天遇到的死锁问题分享

  • 1.service层代码
@Transactional
@Override
public void stopAll(Long appId) {
    // 获取锁
    String lockKey = STOP_RUN_LOCK_PRE + appId;
    while (!lockService.lock(lockKey)) {
        // 获取锁的过程可能操作数据库的次数太多,后期优化
    }

    log.info(lockKey+"上锁:"+ Thread.currentThread().getName());
    try {
        //----------------------业务代码
    } finally {
        // 释放锁
        lockService.unLock(lockKey);
        log.info(lockKey+"释放锁:"+ Thread.currentThread().getName());
    }
}
  • 2.上锁和解锁代码
/**
 * 上锁(上锁成功返回true)
 * @param key
 */
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public Boolean lock(String key) {
    Lock lock = new Lock();
    lock.setLockKey(key).setCreateTime(new Date());
    // 上锁成功返回true 失败返回false
    try {
        return this.save(lock);
    }catch (Exception e){
        return false;
    }
}
/**
 * 释放锁(有可能释放返回的是false,代表释放的锁不存在)
 * @param key
 */
@Override
public Boolean unLock(String key) {
    Lock lock = new Lock();
    lock.setLockKey(key);
    return this.removeById(key);
}
  • 3.定时清理超时锁的代码
/**
 * 清楚超时锁(清除超时30分钟的锁)
 */
@Override
public void cleanLock() {
    LambdaQueryWrapper<Lock> tWrapper = new LambdaQueryWrapper<>();
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.MINUTE,-5);
    tWrapper.lt(Lock::getCreateTime, calendar.getTime());
    this.remove(tWrapper);

}
  • 4.事情大概描述:图片代码块中的业务代码发生报错,然后发生回滚,但锁没有释放。
  • 5.首先注意三个地方,service层方法加了@Transactional注解,加锁的方法加了@Transactional(propagation = Propagation.REQUIRES_NEW),解锁的方法没有使用到事务。也就是说当service层的业务代码发生报错,加锁方法是不会回滚的(这里可以去了解一下spring的事务传播机制),而解锁的方法是会跟着回滚。
  • 6.回滚的经过:这里有大神可能会注意到finally里面执行了解锁,无论什么报错锁最后都会释放。这里解释一下,spring的事务是切面的,会在方法执行完后再进行回滚。
graph TD
业务代码报错 --> 执行finally代码块解锁 --> spring事务回滚导致解锁操作也被回滚

7.定时解锁这里我个人觉得是有点鸡肋的,等30分钟解锁,然后下一个请求进来报错,又要等30分钟。。。。