一、实现图
二、实现原理
当A、B两个线程同时想要操作资源key时,两者只有一个能够成功获取到资源Key。假如A线程获取到了资源key,A线程就会对资源进行锁定并对资源进行操作,在操作的同时开启一个守护线程,定时监听业务执行情况,若A线程锁即将过期但业务并未执行完,守护线程会对锁进行延时。直到A线程执行完业务释放锁资源。
未抢到锁资源的B线程会尝试重新获取资源key,只有当A线程释放锁资源之后,B线程才能抢到资源Key并对其进行操作。
三、注意事项
针对锁的过期时间设置需要根据业务进行特别处理。设置过程与过短都会对服务器资源造成不必要的消耗,建议根据实际业务处理时间设置超出1/3的时间。最短不建议低于10秒。
四、代码实现
4.1:依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.19.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-commons</artifactId>
<version>1.3.2</version>
</dependency>
4.2:工具类
RedisService
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Description :redis工具类
* Date :2023-03-17 16:13:55
* Author :FM
*/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisService
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 基于setNx实现的set方法
*
* @param key 缓存的键值
* @param value 缓存的值
* @return true=设置成功;false=设置失败
*/
public boolean setIfAbsent(final String key, final String value)
{
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key)
{
return redisTemplate.getExpire(key);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key)
{
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public boolean deleteObject(final Collection collection)
{
return redisTemplate.delete(collection) > 0;
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null)
{
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 删除Hash中的某条数据
*
* @param key Redis键
* @param hKey Hash键
* @return 是否成功
*/
public boolean deleteCacheMapValue(final String key, final String hKey)
{
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
RedisLockGuard
/**
* Description :Redis锁延时守护线程
* Date :2024/1/10
* Author :FM
*/
@Slf4j
public class RedisLockGuard implements Runnable{
private String key;
private int expireTime;
private boolean isRunning;
private RedisLock redisLock;
public RedisLockGuard(String key,int expireTime,RedisLock redisLock) {
this.expireTime = expireTime;
this.isRunning = Boolean.TRUE;
this.redisLock = redisLock;
this.key = key;
}
@Override
public void run() {
long nano = System.nanoTime();
long timeout = 3 * expireTime * 1000 * 1000 * 1000L;//最多持续锁超时时间的3倍,expireTime时间单位是秒,转化成微秒
long waitTime = expireTime * 1000 * 2 / 3;// 线程等待多长时间后执行,expireTime时间单位是秒,转化成毫秒
while (isRunning) {
if((System.nanoTime()-nano)>=timeout){
this.stop();
log.error("RedisLock延时守护已超时:{}",key);
continue;
}
try {
Thread.sleep(waitTime);
if (redisLock.lockDelay(expireTime)) {
log.info("RedisLock延时守护延时成功:{}",key);
} else {
log.info("RedisLock延时守护已关闭:{}",key);
this.stop();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void stop() {
this.isRunning = Boolean.FALSE;
}
}
RedisLock
import com.ruoyi.common.core.utils.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Description :Redis锁
* Date :2024/1/10
* Author :FM
*/
@Slf4j
public class RedisLock {
private RedisService redisService= SpringUtils.getBean(RedisService.class);
/** 锁前缀*/
private static final String LOCK_PREFIX = "redis:Lock:";
/** 加锁标志 */
private String lockedFlag;
/** 毫秒与毫微秒的换算单位 1毫秒=1000000毫微秒. */
private static final long MILLI_NANO_CONVERSION = 1000*1000L;
/** 默认超时时间(毫秒). */
private static final long DEFAULT_TIME_OUT = 10*1000;
private static Random RANDOM = new Random();
/** 锁的超时时间(秒),过期删除. */
private static final int EXPIRE = 10;
/**锁键**/
private String key;
/** 锁状态标志. */
private boolean locked = false;
/**
* 获取锁对象
* @param key
* @return
*/
public static RedisLock getRedisLock(String key){
return new RedisLock(key, UUID.randomUUID().toString());
}
/**
* 构造函数.
* @param key key
*/
private RedisLock(String key,String lockedFlag) {
this.key = LOCK_PREFIX+key;
this.lockedFlag=lockedFlag;
}
/**
* 获取锁
* @return true:成功;false:失败
*/
public boolean lock() {
return lock(DEFAULT_TIME_OUT);
}
/**
* 释放锁
* 无论加锁是否成功,都需要调用该方法进行释放锁
*/
public void unlock() {
if (locked) {
//判断锁lockedFlag是否一致,防止释放同key的其他锁
String value = redisService.getCacheObject(key);
if(lockedFlag.equals(value)) redisService.deleteObject(key);
locked=false;
}
log.info("RedisLock解锁:{}", key);
}
/**
* 加锁
* @param timeout 获取锁自旋等待时间(毫秒)
* @return true:成功;false:失败
*/
public boolean lock(long timeout) {
return lock(timeout, EXPIRE);
}
/**
* 加锁.
* @param timeout 获取锁自旋等待时间(毫秒)
* @param expire 锁的超时时间(秒),过期删除
* @return true:成功;false:失败
*/
public boolean lock(long timeout, int expire) {
long nano = System.nanoTime();
timeout *= MILLI_NANO_CONVERSION;
try {
if(timeout>0){
while ((System.nanoTime()-nano)<timeout) {
if (redisService.setIfAbsent(key,lockedFlag)) {
if(expire>0){
redisService.expire(key, expire, TimeUnit.SECONDS);
}
locked = true;
log.info("RedisLock锁定:{},线程{}", key,Thread.currentThread().getName());
//开启守护线程
startGuard(expire);
return locked;
}
// 短暂休眠,避免出现死锁
Thread.sleep(3, RANDOM.nextInt(500));
log.info("RedisLock等待资源:{},线程{}", key,Thread.currentThread().getName());
}
}else{
if (redisService.setIfAbsent(key,lockedFlag)) {
if(expire>0){
redisService.expire(key, expire, TimeUnit.SECONDS);
}
locked = true;
//开启守护线程
startGuard(expire);
return locked;
}
}
} catch (Exception e) {
throw new RuntimeException("Locking error", e);
}
return false;
}
//开启守护线程
private void startGuard(int expire){
RedisLockGuard redisLockGuard = new RedisLockGuard(key, expire, this);
Thread thread = new Thread(redisLockGuard);
//设置为守护线程,所服务的用户线程不存在时自动无效
thread.setDaemon(Boolean.TRUE);
thread.start();
}
/**
* 锁延时
* @param expireTime
* @return
*/
public Boolean lockDelay(int expireTime) {
if(locked){
//判断锁lockedFlag是否一致
String value = redisService.getCacheObject(key);
if(lockedFlag.equals(value)) {
redisService.redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
return true;
}
}
return false;
}
}
4.3:业务实现(服务层)
@Autowired
private RedisTemplate redisTemplate;
/*** redis分布式锁业务实现
* @Author FM
* @Date 2024/1/10
* @param id
**/
@Override
@Transactional
public Result updateTest(String id) {
log.info("分布式抢锁:===>【{}】",id);
//资源key
String key = "redis:key:"+id;
//生成唯一标识
String uuid = id + Thread.currentThread().getId() ;
while (true){
log.info("抢锁线程信息:===>【{}】",uuid);
RedisLock redisLock = RedisLock.getRedisLock(uuid);
boolean locked = redisLock.lock();
log.info("使用当前线程【{}】给redis上锁:===>【{}】",uuid,locked);
int flag = 0;
if(locked){
try {
flag = 0;
log.info("检查单个资源锁信息:===>【{}】",key);
Boolean lockKey = redisTemplate.hasKey(key);
log.info("检查单个资源锁是否存在:===>【{}】",lockKey);
if(lockKey){
//释放自己的锁,然后跳过此次循环
log.info("单个资源被其他线程占用,释放锁资源:===>【{}】",uuid);
redisLock.unlock();
flag = 1;
continue;
}
//以下是测试业务
//测试添加针对该条单个资源的成功消息,根据业务选择释放不释放
redisTemplate.opsForValue().set(key, "exist",2, TimeUnit.MINUTES);
//测试修改状态
int i = mapper.updateSQL(id);
Long beginTime = System.currentTimeMillis();
Long a = 1L;
//模拟业务执行超过过期时间
while (true){
Long endTime = System.currentTimeMillis();
a = endTime - beginTime;
if(a/20000>=1){
break;
}
}
if(i != 1){
throw new Exception("修改数据库条数大于1");
}
if(flag == 0){
break;
}
} catch (Exception e){
log.info("业务报错,事务回滚:===>【{}】",key);
log.info(e.getMessage());
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
} finally {
log.info("业务执行完成,释放整个资源:===>【{}】 ",uuid);
redisLock.unlock();
if(flag == 0){
log.info("业务执行完成,释放单个资源:===>【{}】",key);
redisTemplate.delete(key);
}
}
}
}
return null;
}