java利用redisson实现分布式锁

最近开发一了个答题抽奖项目,由于部署项目采用了负载均衡策略,分配奖品时必须使用分布式锁,项目开发完成后记录一下利用redisson实现分布式锁的过程

一、springboot项目整合redisson

  • redisson pom依赖如下,springboot为2.x,如果是更底版本,redisson可能也需要换成更低的版本。
 <!--Redis分布式锁-->
 <dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson</artifactId>
     <version>3.11.0</version>   
 </dependency>
  • yml配置文件中的redis配置如下
spring: 
	redis:
    host: localhost
    port: 3306
    timeout: 6000
    database: 5
    password: '123456'
    lettuce:
      pool:
        max-active: -1 #最大连接数
        max-idle: 8 #最大空闲数
        max-wait: 15000  #最大连接阻塞等待时间
        min-idle: 0 #最小空闲数
  • 配置RedissonClient

创建RedisProperties类,装配redis配置

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {

	private String host;
	private int port;
	private String password;
	private int database;
	public String getHost() {
		return host;
	}
	public void setHost(String host) {
		this.host = host;
	}
	public int getPort() {
		return port;
	}
	public void setPort(int port) {
		this.port = port;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public int getDatabase() {
		return database;
	}
	public void setDatabase(int database) {
		this.database = database;
	}
}

然后创建RedissionConfig类,配置RedissonClient


import org.apache.log4j.Logger;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissionConfig {
	@Autowired
    private RedisProperties redisProperties;

	private Logger logger = Logger.getLogger(this.getClass());

    @Bean
    public RedissonClient redissonClient() {
        RedissonClient redissonClient;

        Config config = new Config();
        String url = "redis://" + redisProperties.getHost() + ":" + redisProperties.getPort();
        config.useSingleServer().setAddress(url)
                .setPassword(redisProperties.getPassword())
                .setDatabase(redisProperties.getDatabase());

        try {
            redissonClient = Redisson.create(config);
            return redissonClient;
        } catch (Exception e) {
            logger.error("RedissonClient init redis url:[{}], Exception:" + url);
            return null;
        }
    }
}

到此Redisson就整合完成了

二、利用Redisson实现分布式锁

创建DistributedRedisLock类,代码如下

import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @title 分布式redis锁
 * @author gavin
 * @date 2020年7月10日
 */
@Component
public class DistributedRedisLock {
	@Autowired
	private RedissonClient redissonClient;

	private Logger logger = Logger.getLogger(this.getClass());

	// 加锁
	public Boolean lock(String lockName) {
		try {
			if (redissonClient == null) {
				logger.info("redissonClient为空");
				return false;
			}
			RLock lock = redissonClient.getLock(lockName);
			// 锁30秒后自动释放,防止死锁
			lock.lock(30, TimeUnit.SECONDS);

			logger.info("线程" + Thread.currentThread().getName() + "加锁" + lockName + "成功");
			// 加锁成功
			return true;
		} catch (Exception e) {
			logger.error("加锁异常:" + lockName);
			e.printStackTrace();
			return false;
		}
	}

	// 释放锁
	public Boolean unlock(String lockName) {
		try {
			if (redissonClient == null) {
				logger.info("redissonClient为空");
				return false;
			}
			RLock lock = redissonClient.getLock(lockName);

			if (lock.isLocked()) {
				if (lock.isHeldByCurrentThread()) {
					// 主动释放锁
					lock.unlock();
					logger.info("线程" + Thread.currentThread().getName() + "解锁" + lockName + "成功");
					return true;
				}
			}
			return true;
		} catch (Exception e) {
			logger.error(Thread.currentThread().getName() + "解锁异常:" + lockName);
			e.printStackTrace();
			return false;
		}
	}
}

DistributedRedisLock 类中有两个方法,lock是加锁方法,unlock是释放锁方法。其中有两个点需要注意

  1. lock.lock(30, TimeUnit.SECONDS),这行代码的作用是,如果加锁的业务代码运行时间超过了30秒,便会自动释放锁,这是为防止业务代码运行时间过长或者出错导致死锁。
  2. lock.isHeldByCurrentThread(),在释放锁时,这行判断必须要加上,它会判断当前线程释放的锁是否属于当前线程,防止解锁错乱。

三、测试分布式锁

我采用的测试逻辑是,对redis中一个值为100的变量,使用100线程同时对该变量进行减1操作,看最后变量的值是否为0,如果为0,则分布式锁有效。

创建TestLockController测试类,代码如下

import java.util.concurrent.CountDownLatch;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("lock")
public class TestLockController {
	
	@Autowired
	private DistributedRedisLock distributedRedisLock;
	
	// redision分布式锁id
	private static String lock = "lock_id";
	
	// 测试数据key
	private static String dataKey = "lock_count";
	
	// 测试数据value
	private int count = 100;
	
	// 模拟线程数
	private int threadNumber = 100;
	
	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	
	
	/**
	 * @title 在redis中初始化设置lock_count=100,用于测试
	 * @author gavin
	 * @date 2020年8月26日
	 */
	@GetMapping("lockInit")
	public Object testInit() {
		this.stringRedisTemplate.opsForValue().set(dataKey, String.valueOf(this.count));
		return this.stringRedisTemplate.opsForValue().get(dataKey);
	}
	
	/**
	 * @title 测试不加锁
	 * @author gavin
	 * @date 2020年8月26日
	 * @return
	 * @throws InterruptedException
	 */
	@GetMapping("/testNoLock")
	public Object testNoLock() throws InterruptedException {
		// 多线程计数器
		CountDownLatch latch = new CountDownLatch(this.threadNumber);
		for(int i=0;i<this.threadNumber;i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					String valueStr = stringRedisTemplate.opsForValue().get(dataKey);
					int value = Integer.valueOf(valueStr);				
					value = value - 1;
					stringRedisTemplate.opsForValue().set(dataKey, String.valueOf(value));
					// 每个线程执行完毕,计数器减1
					latch.countDown();
				}
			}).start();
		}
		// 在此阻塞,等待所有线程结束
		latch.await();
		System.out.println(this.threadNumber + "个线程全部执行完毕");
		// 返回最终的dataKey
		return stringRedisTemplate.opsForValue().get(dataKey);
	}
	
	/**
	 * @title 测试分布式锁
	 * @author gavin
	 * @date 2020年8月26日
	 * @return
	 * @throws InterruptedException
	 */
	@GetMapping("/testUseLock")
	public Object testLock() throws InterruptedException {
		// 多线程计数器
		CountDownLatch latch = new CountDownLatch(this.threadNumber);
		for(int i=0;i<this.threadNumber;i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					boolean lockFlag = distributedRedisLock.lock(lock);
					// 加分布式锁
					if(lockFlag) {
						String valueStr = stringRedisTemplate.opsForValue().get(dataKey);
						int value = Integer.valueOf(valueStr);				
						value = value - 1;
						stringRedisTemplate.opsForValue().set(dataKey, String.valueOf(value));
						// 加锁
						distributedRedisLock.unlock(lock);
						// 每个线程执行完毕,计数器减1
						latch.countDown();
					}
				}
			}).start();
		}
		// 在此阻塞,等待所有线程结束
		latch.await();
		System.out.println(this.threadNumber + "个线程全部执行完毕");
		// 返回最终的dataKey
		return stringRedisTemplate.opsForValue().get(dataKey);
	}
}

测试类中有三个方法,分别是

  1. lockInit方法,向redis中存储一个key为lock_count,值为100的变量
  2. testNoLock方法,不加分布式锁进行测试
  3. testLock方法,使用分布式锁进行测试

启动项目,打开postman,首先执行lockInit,在redis中初始化变量lock_count=100
在这里插入图片描述
然后我们先测试一下不加分布式锁的testNoLock方法,运行结果如下图
在这里插入图片描述
可以看到,lock_count最终的值为98,说明100个线程运行过程中出现了并发。

再次执行lockInit方法,使lock_count值为100,然后再运行testLock方法,运行结果如下图
在这里插入图片描述
可以看到,lock_count的值为0,说明没有出现并发,分布式锁生效了。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值