java添加新数据时出现重复数据(利用redis加锁)

在我们添加用户时,如果点击过快,可能数据库会出现两个一模一样的数据,这个原因有很多种,

但是我遇到的是因为同时执行了两个线程,同时插入了两个一样的数据,那么我来分享下解决之道!!

这里就要引入分布式锁了,如果使用synchronized这个锁的话,就是锁的力度比较强,锁的范围有点大,此处利用的redis中的redisson这个锁进行操作

首先需要引入redis和redisson的相关jar包

首先先分析redis

1. spring-data-redis

使用 spring-boot-starter-data-redis 操作 redis

1.依赖

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.6.4</version>
</dependency>

2.redis的简单使用:

// 存值
redisTemplate.opsForValue().set("Int",1);
// 取值
String String = (String) redisTemplate.opsForValue().get("String");

其他配置(使支持 RedisTemplate<String,Object>)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

/**
 *  redisTemplate 配置类
 */
@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(RedisSerializer.string());

        return redisTemplate;
    }
}

2. spring-session-data-redis

使用 spring-session-data-redis 自动同步 session 数据到 redis 中,实现 session 共享

  1. 依赖

<!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    <version>2.6.3</version>
</dependency>
  1. 配置

spring:
  # session 配置,过期时间(分钟)
  session:
    timeout: 86400
    store-type: redis

接下来是redisClient配置文件:

@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedissonConfig {
    private String host;
    private String port;

    @Bean
    public RedissonClient redissonClient(){
        // 创建配置
        Config config = new Config();
        String redisAdress=String.format("redis://%s:%s",host,port);
        //setdatebase是指redis库号
        config.useSingleServer().setAddress(redisAdress).setDatabase(3);
        //创建实例
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

application.yml

  redis:
    port: 6379
    host: localhost
    database: 3

其次就是获取锁,以及制订锁的过期时间,-1表示没时间,锁中间执行你需要执行的命令(此处为我程序中的加入队伍为例,同一个用户不能加入多次同一个队伍),之后需要释放锁,这个锁必须要释放,不然会这个线程会一直执行,redisson中的看门狗机制,如果你的任务没有完成,并且你执行的是定时任务们马上过期,他会自动重置过期时间,直到你的任务完成,不然会一直执行下去,其他线程进不来

   RLock lock = redissonClient.getLock("my:join_team");
        try {
            // 抢到锁并执行
            while (true) {
// tryLock (最长取锁时间,超时 false;过期时间,执行时间超过过期时间,锁将释放;时间单位)
    // 过期时间使用 -1 可以续期;第一个参数是人走了,下个人进来的时间,0 表示只执行一次
                if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
                    System.out.println("getLock: " + Thread.currentThread().getId());
                    QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
                    userTeamQueryWrapper.eq("userId", userId);
                    long hasJoinNum = userTeamService.count(userTeamQueryWrapper);
                    if (hasJoinNum > 5) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "最多创建和加入 5 个队伍");
                    }
                    // 不能重复加入已加入的队伍
                    userTeamQueryWrapper = new QueryWrapper<>();
                    userTeamQueryWrapper.eq("userId", userId);
                    userTeamQueryWrapper.eq("teamId", teamId);
                    long hasUserJoinTeam = userTeamService.count(userTeamQueryWrapper);
                    if (hasUserJoinTeam > 0) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户已加入该队伍");
                    }
                    // 已加入队伍的人数
                    long teamHasJoinNum = this.countTeamUserByTeamId(teamId);
                    if (teamHasJoinNum >= team.getMaxNum()) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已满");
                    }
                    // 修改队伍信息
                    UserTeam userTeam = new UserTeam();
                    userTeam.setUserId(userId);
                    userTeam.setTeamId(teamId);
                    userTeam.setJoinTime(new Date());
                    return userTeamService.save(userTeam);
                }
            }
        } catch (InterruptedException e) {
            log.error("doCacheRecommendUser error", e);
            return false;
        } finally {
            // 只能释放自己的锁
            if (lock.isHeldByCurrentThread()) {
                System.out.println("unLock: " + Thread.currentThread().getId());
                lock.unlock();
            }
        }
    }

为什么需要锁呢?

以我的理解,我们每个人都看作一个线程,然后上厕所好比执行应用程序,当应用程序需要执行时,多个线程一拥而上,肯定会出现几个线程同时执行一个程序的情况,会出现脏数据,重复的数据,这就有必要来加一把锁了,加锁就是厕所的门,当应用程序需要执行时,一个厕所只能进入一个人,也就是一个线程,当这个人在上厕所时,其他人只能在外面等着,等这个人上完了其他人才能进去,执行下一个线程,看门狗机制就是说这个人不上完厕所不出来的意思,无限重置过期时间,知道这个人出来为止,这就是我对锁的想法,为了避免一些不必要的麻烦,上线的项目都需要加一些所锁来维持项目的稳定性。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值