目录

1.项目场景:

2.问题代码 :

3.修改后的代码:

4.实战示例:

5.更新:setIfAbsent

6.再次更新:redisson


1.项目场景:

锁主要是用来实现资源共享同步,只有获取到了锁才能访问该同步代码,否则等待其他线程使用结束释放锁。

2.问题代码 :

@Autowired
private RedisTemplate redisTemplate;

/**
 * 加锁
 */
public boolean getLock(String key) {
	 try {
		 long count = redisTemplate.opsForValue().increment(key, 1);
		 //此段代码出现异常则会出现死锁问题,key一直都存在
		 if(count == 1){
			 //设置有效期2秒
			 redisTemplate.expire(key, 2, TimeUnit.SECONDS);
			 return true;
		 }
		 //如果存在表示重复
		 return false;
	 } catch (Exception e) {
		 logger.error("redis加锁异常", e);
		 redisTemplate.delete(key);		//出现异常删除锁
		 return true;
	 }
}

当正常情况没有问题,但是当计数器设置成功后,服务出现异常,那么这个key会永远存在,这样肯定不行。

Redis Incr 命令将 key 中储存的数字值增一,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作,且将key的有效时间设置为长期有效。

3.修改后的代码:

@Autowired
private RedisTemplate redisTemplate;

/**
 * 加锁
 */
public boolean getLock(String key) {
	 try {
		 long count = redisTemplate.opsForValue().increment(key, 1);
		 if(count == 1){
			 //设置有效期2秒
			 redisTemplate.expire(key, 2, TimeUnit.SECONDS);
			 return true;
		 }else{
			 long time = redisTemplate.getExpire(key,TimeUnit.SECONDS);
			 if(time == -1){
				 //设置失败重新设置过期时间
				 redisTemplate.expire(key, 2, TimeUnit.SECONDS);
				 return true;
			 }
		 }
		 //如果存在表示重复
		 return false;
	 } catch (Exception e) {
		 logger.error("redis加锁异常", e);
		 redisTemplate.delete(key);		//出现异常删除锁
		 return true;
	 }
}

4.实战示例:

场景:同一个接口,必须等待上个线程处理完后,业务才能处理。

Service层:RedisLock.java

package com.test.utils;

import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;

/**
 * Redis锁
 */
@Service
@Slf4j
public class RedisLock {
	
	@Autowired
    private RedisTemplate<String, ?> redisTemplate;
	
	/**
	 * 加锁
	 * @param key	key
	 * @param timeExpire	过期时间单位秒
	 * @return	true表示key存在
	 */
	public boolean getLock(String key, long timeExpire) {
         try {
        	 long count = redisTemplate.opsForValue().increment(key, 1);
        	 if(count == 1){
        		 //设置有效期timeExpire
        		 redisTemplate.expire(key, timeExpire, TimeUnit.SECONDS);
        		 return false;
        	 }else{
        		 long time = redisTemplate.getExpire(key,TimeUnit.SECONDS);
        		 if(time == -1){
	        		 //设置失败重新设置过期时间
        			 redisTemplate.expire(key, timeExpire, TimeUnit.SECONDS);
	           		 return false;
        		 }
        	 }
             //如果存在表示重复
             return true;
         } catch (Exception e) {
        	 log.error("redis加锁异常", e);
        	 redisTemplate.delete(key);	//出现异常删除锁
 			 return false;
         }
	}
	
	/**
	 * 解锁
	 * @param key	key
	 * @return	true表示成功
	 */
	public boolean unLock(String key) {
         try {
        	 redisTemplate.delete(key);	//删除key
             return true;
         } catch (Exception e) {
        	 log.error("redis解锁异常", e);
        	 redisTemplate.delete(key);	//出现异常删除锁
 			 return false;
         }
	}
	
}

Controller层:TestController.java

package com.test.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.test.utils.RedisLock;

import lombok.extern.slf4j.Slf4j;


@RestController
@Slf4j
public class TestController{
	
	@Autowired
	private RedisLock redisLock;
		
	/**
	 * 测试并发
	 */
	@RequestMapping(value = "/test", method = RequestMethod.GET)
	public String test(String key) {
		try {
			long expireTime = 30000;	//有效时长(毫秒)
			long beginTime = System.currentTimeMillis();	//开始时间
			
			//判断同一时间是否有多个用户操作
			boolean flagLock = redisLock.getLock(key, 6);
			if(flagLock){
				//如果锁存在,尝试重新获取锁
				while (redisLock.getLock(key, 6)) {
					
					//执行时长(毫秒)
					long time = System.currentTimeMillis() - beginTime;
					//防止死循环
					if(time > expireTime){
						log.error("超时,请稍后重试");
						return "超时,请稍后重试!";
					}
					
					Thread.sleep(100);
				}
			}
			
			//TODO 业务操作...
			return "success";
		} catch (Exception e) {
			log.error("出错:", e);
			return "出错,请联系管理员!";
		}finally {
			//解锁
	    	redisLock.unLock(key);
		}
	}
	
}


5.更新:setIfAbsent

将redis版本升级到2.1以上,可以直接使用setIfAbsent设置过期时间,使用 4 个参数的重载方法是原子性的。

redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);

说明:当key不存在,将key的值设为value,并设置过期时间,返回true;若给定的key已经存在,则不做任何动作,并返回false。此方法是原子性的。

 使用setIfAbsent

@Autowired
private RedisTemplate redisTemplate;

/**
 * 加锁
 * @param key key
 * @param value value
 * @param timeout 过期时间单位秒
 * @param true表示key不存在
 */
public boolean getLock(String key, String value, long timeout) {
	 try {
		 //原子性操作
		 boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
		 //如果存在false表示重复
		 return flag;
	 } catch (Exception e) {
		 logger.error("redis加锁异常", e);
		 redisTemplate.delete(key);		//出现异常删除锁
		 return true;
	 }
}


6.再次更新:redisson

推荐使用Redisson来实现分布式锁,当然前面两种方式也是可行的,条条大路通罗马。

什么是redisson:

Redisson - 是一个高级的分布式协调Redis客服端,Redisson API 侧重于分布式开发,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.7.5</version>
</dependency>

application.properties

# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接密码(默认为空)
spring.redis.password=
# Redis服务器连接端口
spring.redis.port=6379

Redisson的配置类

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import java.io.IOException;
 
@Configuration
public class RedissonConfig {
 
    @Value("${spring.redis.host}")
    private String host;
 
    @Value("${spring.redis.port}")
    private String port;
 
    @Value("${spring.redis.password}")
    private String password;
 
    /**
     * RedissonClient,单机模式
     * @return
     * @throws IOException
     */
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
        return Redisson.create(config);
    }
}

Redisson分布式锁业务类


import java.util.concurrent.TimeUnit;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class TestService {
	@Autowired
    RedissonClient redissonClient;
 
    private final static String LOCK_KEY = "TEST_KEY";
    int count = 50;
 
    public void seckill() {
        //定义锁
        RLock rlock = redissonClient.getLock(LOCK_KEY);
        try {
        	//尝试加锁,最大等待时间30秒,上锁10秒自动解锁
        	boolean flag = rlock.tryLock(30, 10, TimeUnit.SECONDS);
            
            if (flag) {
                log.info("线程:" + Thread.currentThread().getName() + "获得了锁");
                log.info("剩余数量:{}", --count);
            }
        } catch (Exception e) {
            log.error("程序执行异常:", e);
            //释放锁
            rlock.unlock();
        } finally {
            log.info("线程:" + Thread.currentThread().getName() + "准备释放锁");
            //释放锁
            rlock.unlock();
        }
    }
}

源码地址包含三种方式:

https://download.csdn.net/download/u011974797/86772996

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐