前言
面試總是會被問到有沒有用過分散式鎖、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、分散式快取、資料結構等等。