Redisson 分布式锁使用

本文介绍了如何使用Redisson实现分布式锁,包括添加依赖、配置、互斥锁、读写锁、计数器和信号量的使用。通过示例代码展示了Redisson在确保线程安全和资源管理方面的功能。
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值