前言
面试总是会被问到有没有用过分布式锁、redis 锁,大部分读者平时很少接触到,所以只能很无奈的回答“没有”。本文通过 Spring Boot 整合 redisson 来实现分布式锁,并结合 demo 测试结果。
首先看下大佬总结的图
正文
添加依赖
<!--redis-->
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-data-redis
</artifactId>
</dependency>
<!--redisson-->
<dependency>
<groupId>
org.redisson
</groupId>
<artifactId>
redisson-spring-boot-starter
</artifactId>
<version>
3.10.6
</version>
</dependency>
配置信息
spring:
# redis
redis:
host:
47.103
.
5.190
port:
6379
jedis:
pool:
# 连接池最大连接数(使用负值表示没有限制)max-active: 100
# 连接池中的最小空闲连接
max-idle: 10
# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1
# 连接超时时间(毫秒)timeout: 5000
#默认是索引为 0 的数据库
database: 0
配置类
/**
* redisson 配置,下面是单节点配置:*
* @author gourd
*/
@Configuration
publicclass
RedissonConfig
{
@Value
("${spring.redis.host}"
)
private
String
host;
@Value
("${spring.redis.port}"
)
private
String
port;
@Value
("${spring.redis.password:}"
)
private
String
password;
@Bean
public
RedissonClient
redissonClient() {
Config
config =
new
Config
();
// 单节点
config.useSingleServer().setAddress(
"redis://"
+ host +
":"
+ port);
if
(
StringUtils
.isEmpty(password)) {config.useSingleServer().setPassword(null);
}
else
{config.useSingleServer().setPassword(password);
}
// 添加主从配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
// 集群模式配置 setScanInterval() 扫描间隔时间,单位是毫秒, // 可以用 "rediss://" 来启用 SSL 连接
// config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001").addNodeAddress("redis://127.0.0.1:7002");
return
Redisson
.create(config);
}
}
Redisson 工具类
/**
* redis 分布式锁帮助类
*
* @author gourd
*
*/
publicclass
RedisLockUtil
{
privatestatic
DistributedLocker
distributedLocker =
SpringContextHolder
.getBean(
"distributedLocker"
,
DistributedLocker
.
class
);
/**
* 加锁
* @param lockKey
* @return
*/
publicstatic
RLock
lock
(
String
lockKey) {
return
distributedLocker.
lock
(lockKey);
}
/**
* 释放锁
* @param lockKey
*/
publicstaticvoid
unlock(
String
lockKey) {distributedLocker.unlock(lockKey);
}
/**
* 释放锁
* @param lock
*/
publicstaticvoid
unlock(
RLock
lock
) {
distributedLocker.unlock(lock);
}
/**
* 带超时的锁
* @param lockKey
* @param timeout 超时时间 单位:秒
*/
publicstatic
RLock
lock
(
String
lockKey,
int
timeout) {
return
distributedLocker.
lock
(lockKey, timeout);
}
/**
* 带超时的锁
* @param lockKey
* @param unit 时间单位
* @param timeout 超时时间
*/
publicstatic
RLock
lock
(
String
lockKey,
int
timeout,
TimeUnit
unit ) {
return
distributedLocker.
lock
(lockKey, unit, timeout);
}
/**
* 尝试获取锁
* @param lockKey
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
publicstaticboolean
tryLock(
String
lockKey,
int
waitTime,
int
leaseTime) {
return
distributedLocker.tryLock(lockKey,
TimeUnit
.SECONDS, waitTime, leaseTime);
}
/**
* 尝试获取锁
* @param lockKey
* @param unit 时间单位
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
publicstaticboolean
tryLock(
String
lockKey,
TimeUnit
unit,
int
waitTime,
int
leaseTime) {
return
distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);
}
/**
* 获取计数器
*
* @param name
* @return
*/
publicstatic
RCountDownLatch
getCountDownLatch(
String
name){
return
distributedLocker.getCountDownLatch(name);
}
/**
* 获取信号量
*
* @param name
* @return
*/
publicstatic
RSemaphore
getSemaphore(
String
name){
return
distributedLocker.getSemaphore(name);
}
}
底层封装
/**
* @author gourd
*/
publicinterface
DistributedLocker
{
RLock
lock
(
String
lockKey);
RLock
lock
(
String
lockKey,
int
timeout);
RLock
lock
(
String
lockKey,
TimeUnit
unit,
int
timeout);
boolean
tryLock(
String
lockKey,
TimeUnit
unit,
int
waitTime,
int
leaseTime);
void
unlock(
String
lockKey);
void
unlock(
RLock
lock
);
}
/**
* @author gourd
*/
@Component
publicclass
RedisDistributedLocker
implements
DistributedLocker
{
@Autowired
private
RedissonClient
redissonClient;
@Override
public
RLock
lock
(
String
lockKey) {
RLock
lock
= redissonClient.getLock(lockKey);
lock
.
lock
();
returnlock
;
}
@Override
public
RLock
lock
(
String
lockKey,
int
leaseTime) {
RLock
lock
= redissonClient.getLock(lockKey);
lock
.
lock
(leaseTime,
TimeUnit
.SECONDS);
returnlock
;
}
@Override
public
RLock
lock
(
String
lockKey,
TimeUnit
unit ,
int
timeout) {
RLock
lock
= redissonClient.getLock(lockKey);
lock
.
lock
(timeout, unit);
returnlock
;
}
@Override
publicboolean
tryLock(
String
lockKey,
TimeUnit
unit,
int
waitTime,
int
leaseTime) {
RLock
lock
= redissonClient.getLock(lockKey);
try
{
returnlock
.tryLock(waitTime, leaseTime, unit);
}
catch
(
InterruptedException
e) {
returnfalse
;
}
}
@Override
publicvoid
unlock(
String
lockKey) {
RLock
lock
= redissonClient.getLock(lockKey);
lock
.unlock();}
@Override
publicvoid
unlock(
RLock
lock
) {
lock
.unlock();}
}
测试
模拟并发测试
/**
* redis 分布式锁控制器
* @author gourd
* @since 2019-07-30
*/
@RestController
@Api
(tags =
"redisson"
, description =
"redis 分布式锁控制器"
)
@RequestMapping
("/redisson")
@Slf4j
publicclass
RedissonLockController
{
/**
* 锁测试共享变量
*/
private
Integer
lockCount =
10
;
/**
* 无锁测试共享变量
*/
private
Integer
count =
10
;
/**
* 模拟线程数
*/
privatestaticint
threadNum =
10
;
/**
* 模拟并发测试加锁和不加锁
* @return
*/
@GetMapping
("/test")
@ApiOperation
(value =
"模拟并发测试加锁和不加锁"
)
publicvoidlock
(){
// 计数器
final
CountDownLatch
countDownLatch =
new
CountDownLatch
(1);
for
(
int
i =
0
; i < threadNum; i ++) {
MyRunnable
myRunnable =
new
MyRunnable
(countDownLatch);
Thread
myThread =
new
Thread
(myRunnable);
myThread.start();}
// 释放所有线程
countDownLatch.countDown();}
/**
* 加锁测试
*/
privatevoid
testLockCount() {
String
lockKey =
"lock-test"
;
try
{
// 加锁,设置超时时间 2s
RedisLockUtil
.
lock
(lockKey,
2
,
TimeUnit
.SECONDS);
lockCount--;
log.info(
"lockCount 值:"
+lockCount);
}
catch
(
Exception
e){log.error(e.getMessage(),e);
}
finally
{
// 释放锁
RedisLockUtil
.unlock(lockKey);
}
}
/**
* 无锁测试
*/
privatevoid
testCount() {
count--;
log.info(
"count 值:"
+count);
}
publicclass
MyRunnable
implements
Runnable
{
/**
* 计数器
*/
final
CountDownLatch
countDownLatch;
public
MyRunnable
(
CountDownLatch
countDownLatch) {
this
.countDownLatch = countDownLatch;
}
@Override
publicvoid
run() {
try
{
// 阻塞当前线程,直到计时器的值为 0
countDownLatch.
await
();}
catch
(
InterruptedException
e) {log.error(e.getMessage(),e);
}
// 无锁操作
testCount();
// 加锁操作
testLockCount();}
}
}
调用接口后打印值:
测试结果
根据打印结果可以明显看到,未加锁的 count– 后值是乱序的,而加锁后的结果和我们预期的一样。
由于条件问题没办法测试分布式的并发。只能模拟单服务的这种并发,但是原理是一样,希望对大家有帮助。如有错误之处,欢迎指正。
最后
私信回复 资料 领取一线大厂 Java 面试题总结 + 各知识点学习思维导 + 一份 300 页 pdf 文档的 Java 核心知识点总结!
这些资料的内容都是面试时面试官必问的知识点,篇章包括了很多知识点,其中包括了有基础知识、Java 集合、JVM、多线程并发、spring 原理、微服务、Netty 与 RPC、Kafka、日记、设计模式、Java 算法、数据库、Zookeeper、分布式缓存、数据结构等等。