一、引入依赖
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
二、锁的相关概念
一把合格的锁应该具备以下几个方面:
1.互斥:锁具有独占性,一把锁在同一时刻只能有一个持有者。
2.安全:安全指的是解锁时的安全性,只能解锁自己持有的锁。
3.不死锁:不能因为意外的发生,导致锁不能被正常的释放。
三、实现分布式锁
工具类:
package com.example.springbootdemo.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @author qx
* @date 2023/07/16
* @desc Redis分布式锁工具
*/
@Component
public class RedisLock {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 加锁
*
* @param key 键
* @param value 值
* @param timeOut 过期时间
* @return 设置成功返回1,设置失败返回0
*/
public boolean lock(String key, String value, Long timeOut) {
return stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeOut, TimeUnit.SECONDS);
}
/**
* 解锁
*
* @param key 键
*/
public void unLock(String key) {
stringRedisTemplate.delete(key);
}
}
测试类:
package com.example.springbootdemo.controller;
import com.example.springbootdemo.util.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author qx
* @date 2023/07/16
* @desc
*/
@RestController
@RequestMapping("/redis")
@Slf4j
public class RedisController {
private static final Long TIME_OUT = 60L;
private static final String REDIS_KEY = "redis_lock";
@Autowired
private RedisLock redisLock;
@GetMapping("/lock")
public String lock(String value) {
//加锁
boolean isLock = redisLock.lock(REDIS_KEY, value, TIME_OUT);
if (!isLock) {
throw new RuntimeException("当前请求的资源已被占用,请稍后再试");
}
//处理其他逻辑
log.info("处理其他逻辑");
//解锁
redisLock.unLock(REDIS_KEY);
return "success";
}
}
我们启动程序,访问这个请求地址,如果多个线程访问我们的地址的话,可以实现同一时刻只有一个持有者进行操作。
2023-07-16 09:17:33.500 INFO 6256 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2023-07-16 09:17:33.502 INFO 6256 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
2023-07-16 09:17:34.726 INFO 6256 --- [nio-8080-exec-1] c.e.s.controller.RedisController : 处理其他逻辑
四、改进版本
假如有两个线程A和B,在A执行完某些操作后,恰好key到了过期时间,而这时B获取到了锁,那么接下来会发生一个情况就是A会执行unLock方法将B获得的锁删除掉,我们该如何解决这个问题?
我们需要在删除这个锁之前判断一下是不是自己持有的锁,如果是就删除操作,如果不是自己的锁或者锁过期那么就不在去删除。
package com.example.springbootdemo.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author qx
* @date 2023/07/16
* @desc Redis分布式锁工具
*/
@Component
public class RedisLock {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 加锁
*
* @param key 键
* @param value 值
* @param timeOut 过期时间
* @return 设置成功返回1,设置失败返回0
*/
public boolean lock(String key, String value, Long timeOut) {
return stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeOut, TimeUnit.SECONDS);
}
/**
* 解锁
*
* @param key 键
*/
public void unLock(String key, String value) {
String oldValue = stringRedisTemplate.opsForValue().get(key);
if (Objects.nonNull(oldValue) && oldValue.equals(value)) {
stringRedisTemplate.delete(key);
}
}
}
package com.example.springbootdemo.controller;
import com.example.springbootdemo.util.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author qx
* @date 2023/07/16
* @desc
*/
@RestController
@RequestMapping("/redis")
@Slf4j
public class RedisController {
private static final Long TIME_OUT = 60L;
private static final String REDIS_KEY = "redis_lock";
@Autowired
private RedisLock redisLock;
@GetMapping("/lock")
public String lock(String value) {
//加锁
boolean isLock = redisLock.lock(REDIS_KEY, value, TIME_OUT);
if (!isLock) {
throw new RuntimeException("当前请求的资源已被占用,请稍后再试");
}
//处理其他逻辑
log.info("处理其他逻辑");
//解锁 加上自己的value
redisLock.unLock(REDIS_KEY, value);
return "success";
}
}
五、其他实现方案
其他实现方案我们后续再学习。
1.Lua脚本
Redis通过调用Lua脚本,可以实现更加强大和复杂的功能,在执行Lua脚本时该操作具有原子性。这恰好可以用来实现分布式锁,将多个操作封装到一个脚本中,可以让多个操作具备原子性。
2.Redission
Redission是一个基于Redis的第三方组件,是官方推荐的分布式锁解决方案。所以在生产环境中我们更加推荐使用Redission来作为我们分布式锁的方案。