什么是锁、分布式锁?

锁:由于资源有限,我们要控制同一时间,只有某些线程能访问到资源。

Java 实现锁:synchronized 关键字、并发包中的类。

场景举例:用户加入队伍,如果多个线程同时执行了加入队伍的代码,那么就会出现用户重复加入队伍的问题。所以,我们要加锁,同一时间,只允许一个线程执行加入队伍的代码。

存在问题:只对单个 JVM 有效,如果程序部署在多台服务器上,还会出现资源访问的问题。

所以,我们需要使用分布式锁,控制分布式系统的资源访问。

Redisson 实现分布式锁

Redisson 是一个 Java 操作 Redis 的客户端,提供了大量的分布式数据集来简化对 Redis 的操作和使用,可以让开发者像使用本地集合一样使用 Redis,完全感知不到 Redis 的存在。

配置 Redisson

https://github.com/redisson/redisson#quick-start

  1. 引入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.32.0</version>
    </dependency>
  2. 编写配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Configuration
    @ConfigurationProperties(prefix = "spring.redis")
    @Data
    public class RedissonConfig {

    private String host;

    private String port;

    @Bean
    public RedissonClient redissonClient() {
    // 1.创建配置
    Config config = new Config();
    String redisAddress = String.format("redis://%s:%s", host, port);
    // 使用单个 Redis,没有开集群
    config.useSingleServer().setAddress(redisAddress).setDatabase(1);
    // 2.创建实例
    return Redisson.create(config);
    }
    }
  3. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Resource
    private RedissonClient redissonClient;

    @Test
    void test() {
    // list
    RList<String> rList = redissonClient.getLis ("test-list");
    rList.add("shameyang");
    System.out.println("rList: " + rList.get(0));
    rList.remove(0);
    // map
    RMap<String, Integer> rMap = redissonClientgetMap ("test-map");
    rMap.put("yang", 10);
    System.out.println("rMap: " + rMap.ge("yang"));
    rMap.remove("yang");
    }

实现分布式锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 创建锁实例
RLock lock = redissonClient.getLock("lock-name");
try {
// 只有一个线程可以获取到锁
// tryLock(long waitTime, long leaseTime, TimeUnit)
if (lock.tryLock(0, 30000L, TimeUnit.MILLISECONDS) {
// 获取到锁后的操作
...
}
} catch (InterruptedException e) {
System.out.println(e.getMessage);
} finally {
// 只能释放自己的锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}

存在逾期问题:如果加锁的代码没有执行完,锁释放了,其他线程也会获取锁,执行加锁代码。

逾期问题(看门狗机制)

针对逾期问题,Redisson 中提供了看门狗机制,即自动续期机制。

使用该机制,需要将 tryLock 的 leaseTime 参数设置为 -1

原理:

  • 监听当前线程,默认过期时间为 30s,每 10s 续期一次(补到 30)
  • 如果线程挂掉(debug 时间过长也会被当成服务器宕机),则不会续期