Redis 分布式锁使用
上一篇尝试了对分布式锁实现的思考,发现要实现一把分布式锁并不简单,好在 Redisson 实现了对于分布式锁的要求。本章主要介绍 Redisson 中分布式锁的使用。
添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.skystep</groupId>
<artifactId>lock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>lock</name>
<description>lock</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springboot整合redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.skystep.lock.LockApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
添加配置
spring:
application:
name: lock
redis:
host: 139.9.189.167
port: 6379
password: null
database: 1
timeout: 30000
server:
port: 8080
RedissonConfig实现
RedissonConfig 用于配置 redis 服务器参数,并且将 RedissonClient 注入到 spring 容器中。
package com.skystep.lock.config;
import io.micrometer.core.instrument.util.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
String redisHost;
@Value("${spring.redis.port}")
String redisPort;
@Value("${spring.redis.password}")
String redisPassword;
@Value("${spring.redis.timeout}")
Integer redisTimeout;
/**
* Redisson配置
* @return RedissonClient
*/
@Bean
RedissonClient redissonClient() {
Config config = new Config();
redisHost = redisHost.startsWith("redis://") ? redisHost : "redis://" + redisHost;
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(redisHost + ":" + redisPort)
.setTimeout(redisTimeout);
if (StringUtils.isNotBlank(redisPassword)) {
serverConfig.setPassword(redisPassword);
}
return Redisson.create(config);
}
}
互斥锁
编写测试类,模拟多线程累计,每个线程将 sum 从0到20000座累加,10个线程得出的结果是200000,对照使用分布式锁和不使用分布式锁的结果。
package com.skystep.lock;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Slf4j
class LockApplicationTests {
@Autowired
RedissonClient redissonClient;
RLock lock;
private static int sum = 0;
private static final int THREAD_CNT = 10;
private void sum() {
for (int j = 0; j < 20000; j++) {
++sum;
}
}
private void sumLock() {
lock = redissonClient.getLock("sum-lock");
lock.lock();
try {
for (int j = 0; j < 20000; j++) {
++sum;
}
} finally {
lock.unlock();
}
}
@Test
// 不使用分布式锁测试
void sumTest() {
// 创建10个线程对sum进行累计,每个线程加20000,
Thread[] threads = new Thread[THREAD_CNT];
for (int k = 0; k < THREAD_CNT; k++) {
threads[k] = new Thread(this::sum);
threads[k].start();
}
// 等待线程执行完毕
for (int k = 0; k < THREAD_CNT; k++) {
try {
threads[k].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印结果,正常应该是200000
log.info("sum:{}", sum);
}
@Test
// 使用分布式锁测试
void sumLockTest() {
// 创建10个线程对sum进行累计,每个线程加20000,
Thread[] threads = new Thread[THREAD_CNT];
for (int k = 0; k < THREAD_CNT; k++) {
threads[k] = new Thread(this::sumLock);
threads[k].start();
}
// 等待线程执行完毕
for (int k = 0; k < THREAD_CNT; k++) {
try {
threads[k].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印结果,正常应该是200000
log.info("sum:{}", sum);
}
}
运行 sumLockTest 得出结果:
2022-06-19 20:12:08.663 INFO 28036 --- [main] com.skystep.lock.LockApplicationTests : sum:200000
运行 sumTest 得出结果:
2022-06-19 20:13:12.190 INFO 30600 --- [main] com.skystep.lock.LockApplicationTests : sum:29750
从两个结果对比,使用分布式锁在多线程环境下,可以保证线程数据安全,保证互斥性。其使用和 Java 工具类 java.util.concurrent 中的 ReentrantLock 相同。
读写锁
如果写锁没完成,就读取不到数据。保证能读到的永远是最新数据。
@SneakyThrows
@Test
public void write() {
// 获取读写锁
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
RLock rLock = lock.writeLock();
try {
rLock.lock();
Thread.sleep(30000);
redisTemplate.opsForValue().set("writeValue", 1);
} finally {
// 释放锁
rLock.unlock();
}
}
@Test
public void read() {
// 获取读写锁
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
RLock rLock = lock.readLock();
// 加读锁,写锁没完成,就读不了
rLock.lock();
try {
Integer writeValue = (Integer)redisTemplate.opsForValue().get("writeValue");
log.info("读锁返回的值:{}", writeValue);
} finally {
rLock.unlock();
}
}
先运行 write 测试,在运行 read 测试,由于 write 函数睡眠 30s,read 函数 也会进入阻塞,直到 write 函数写数据完成,read 函数成功拿到写入的值,从而保证能读到的永远是最新数据。
- 先加写锁,后加读锁,此时并不会立刻给数据加读锁,而是需要等待写锁释放后,才能加读锁;
- 先加读锁,再加写锁:有读锁,写锁需要等待;
- 先加读锁,再加读锁:并发读锁相当于无锁模式,会同时加锁成功;
只要有写锁的存在,都必须等待,写锁是一个排他锁,只能有一个写锁存在,读锁是一个共享锁,可以有多个读锁同时存在。
计数器
开启计数器,只有当计数器数量减少为 0 时,线程才会往下执行。和 java.util.concurrent.CountDownLatch 的使用相同,常用于将异步转化同步。
@Test
public void count() throws InterruptedException {
RCountDownLatch latch = redissonClient.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();
}
@Test
public void countDown() {
RCountDownLatch latch = redissonClient.getCountDownLatch("anyCountDownLatch");
latch.countDown();
}
先运行 count 函数,当运行到 latch.await() 时线程会进入阻塞状态,再运行 countDown 函数,执行 latch.countDown() 计数器减 1,这是可以看到 count 函数继续执行。
信号量
信号量,可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。通常用于资源有明确访问数量限制的场景,常用于限流 。比如停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。
@Test
public void setCount() {
// 设置这个停车场有三个车位
redisTemplate.opsForValue().set("park",3);
}
@Test // 占用一个,如果3个都占用了,就锁住,一直等待
public void park() throws InterruptedException {
RSemaphore park = redissonClient.getSemaphore("park");
park.acquire();
}
@Test // 释放一个
public void go() throws InterruptedException {
RSemaphore park = redissonClient.getSemaphore("park");
park.release();
}
先运行 setCount 函数,模拟这个停车场有三个车位,再运行 park 模拟申请车位,由于当前没有释放的车位,会进入阻塞,运行 go 函数,park 放弃阻塞继续运行。
代码地址
如果没有积分可以关注并且私聊我 https://download.youkuaiyun.com/download/skystep/85698350
本文介绍了如何使用Redisson实现分布式锁,包括添加依赖、配置、互斥锁、读写锁、计数器和信号量的使用。通过示例代码展示了Redisson在确保线程安全和资源管理方面的功能。
739

被折叠的 条评论
为什么被折叠?



