源码下载
Demooo/springboot-demo/src/main/java/com/example/redisthreadsafe at master · cbeann/Demooo · GitHub
线程不安全举例
下面的代码基本就是大众的逻辑,但是有些代码在并发情况下,就会出现错误。以下面代码为例子,如果请求超过阈值LIMIT=10,请求就返回0。
现在考虑这样的一种的一种情况,两个线程同时第一次访问该接口,即大家到步骤2的时候num都是0,那么同时继续往下,那是不是这两个线程执行完毕后,你却发现redis里值为1 ,这就出现了线程不安全的问题。
@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
单实例线程安全没有问题,多实例还是数据不一致。
@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; } }
加分布式锁:伪代码
加锁的问题就是性能低,具有排他性
程安全实例:基于Lua脚本
lua脚本,所有的命令为原子性
--根据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代码
@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; }