前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Redis的几种线程安全的方式

使用Redis的几种线程安全的方式

作者头像
CBeann
发布2023-12-25 18:51:52
2970
发布2023-12-25 18:51:52
举报
文章被收录于专栏:CBeann的博客CBeann的博客

场景

我经常使用Redis,比如有一个常见的场景就是获取key的值,如果小于某个阈值,就加一并且将加一后的值重新set回redis,返回true,否则返回false。就这样简单额场景,其中也牵扯到线程安全的问题。

摊牌了,其实一些复杂的与Redis交互业务逻辑用LUA脚本可以保证原子性。

源码下载

Demooo/springboot-demo/src/main/java/com/example/redisthreadsafe at master · cbeann/Demooo · GitHub

线程不安全举例

下面的代码基本就是大众的逻辑,但是有些代码在并发情况下,就会出现错误。以下面代码为例子,如果请求超过阈值LIMIT=10,请求就返回0。

现在考虑这样的一种的一种情况,两个线程同时第一次访问该接口,即大家到步骤2的时候num都是0,那么同时继续往下,那是不是这两个线程执行完毕后,你却发现redis里值为1 ,这就出现了线程不安全的问题。

代码语言:javascript
复制
 @GetMapping("/notThreadSafe")
    public Object notThreadSafe() {

        //步骤1:拼接key
        int foodId = 1;
        String key = "stock:" + foodId;
        //阈值3
        final int LIMIT = 10;


        //步骤2:获取redis里存的值
        String s = stringRedisTemplate.opsForValue().get(key);
        int num = 0;
        if (!StringUtils.isEmpty(s)) {
            num = Integer.parseInt(s);
        }

        //步骤3:主判断逻辑
        if (num < LIMIT) {
            num++;
            stringRedisTemplate.opsForValue().set(key, String.valueOf(num));
            return 1;
        }

        return 0;


    }

加锁synchronized

单实例线程安全没有问题,多实例还是数据不一致。

代码语言:javascript
复制
@GetMapping("/singleInstanceThreadSafe")
public Object notThreadSafe() {

    //步骤1:拼接key
    int foodId = 1;
    String key = "stock:" + foodId;
    //阈值3
    final int LIMIT = 10;


   synchronized (this){
       //步骤2:获取redis里存的值
       String s = stringRedisTemplate.opsForValue().get(key);
       int num = 0;
       if (!StringUtils.isEmpty(s)) {
           num = Integer.parseInt(s);
       }

       //步骤3:主判断逻辑
       if (num < LIMIT) {
           num++;
           stringRedisTemplate.opsForValue().set(key, String.valueOf(num));
           return 1;
       }

       return 0;
   }


}

加分布式锁:伪代码

参考:基于redis的分布式锁_CBeann的博客-CSDN博客

加锁的问题就是性能低,具有排他性

程安全实例:基于Lua脚本

lua脚本,所有的命令为原子性

代码语言:javascript
复制
--根据key判断是否存在
local key = redis.call("EXISTS", KEYS[1])
--存在key
if tonumber(key) == 1 then
    --获取key的值
    local number = redis.call("GET", KEYS[1])
    --key的值小于阈值
    if tonumber(number) < tonumber(ARGV[1]) then
        redis.call("incrby", KEYS[1], ARGV[2])
        return 1
    else
        return 0
    end

else
    --不存在
    redis.call("SET", KEYS[1], ARGV[2])
    return 1
end

Java代码

代码语言:javascript
复制
@GetMapping("/threadSafe")
    public Object threadSafe() {

        //步骤1:拼接key
        int foodId = 1;
        String key = "stock:" + foodId;
        //阈值3
        final int LIMIT = 10;


        DefaultRedisScript<Object> defaultRedisScript = new DefaultRedisScript<>();
        defaultRedisScript.setResultType(Object.class);
        defaultRedisScript.setScriptText("--根据key判断是否存在\n"
                + "local key = redis.call(\"EXISTS\", KEYS[1])\n"
                + "--存在key\n"
                + "if tonumber(key) == 1 then\n"
                + "    --获取key的值\n"
                + "    local number = redis.call(\"GET\", KEYS[1])\n"
                + "    --key的值小于阈值\n"
                + "    if tonumber(number) < tonumber(ARGV[1]) then\n"
                + "        redis.call(\"incrby\", KEYS[1], ARGV[2])\n"
                + "        return 1\n"
                + "    else\n"
                + "        return 0\n"
                + "    end\n"
                + "\n"
                + "else\n"
                + "    --不存在\n"
                + "    redis.call(\"SET\", KEYS[1], ARGV[2])\n"
                + "    return 1\n"
                + "end");

        List<String> keys = new ArrayList<>();
        keys.add(key);
        Object[] args = new Object[2];
        args[0] = LIMIT;//阈值
        args[1] = 2;//每次加几

        Object execute = redisTemplate.execute(defaultRedisScript, keys, args);
        System.out.println(execute);




        return execute;


    }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景
  • 源码下载
  • 线程不安全举例
  • 加锁synchronized
  • 加分布式锁:伪代码
  • 程安全实例:基于Lua脚本
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档