​ 我们之前在实现一人一单的功能时,为了防止线程安全问题,解决方案为使用synchronized关键字将用户id作为锁对象,保证同一个用户只能有效发送一次请求。

​ 但是这种加锁方式只能在单体项目中有效,对于集群项目,也就是有多台服务器的项目,这种加锁方式会失效。主要原因在于,synchronized的原理其实是依靠JVM中的锁监视器,该监视器会查看是否已经有某个线程占用了锁,如果锁已经被占用,那么其他线程也就无法获取锁。

​ 但在集群模式下,不同的机器拥有不同的JVM,JVM中存储的信息自然也不相同。因此依靠synchronized实现的锁机制自然也会失效。

​ 解决方案为使用分布式锁,实现分布式锁的核心就是让锁监视器的监视范围由一台机器扩展为多台机器。

image-20240310202848044

​ 因此锁监视器必然不能是机器中的部件。目前,主要有3种实现分布式锁的方案:MySql、Redis和Zookeeper

image-20240310202919962

​ 本项目使用Redis实现分布式锁,使用Redis实现锁的机制其实在解决缓存击穿问题时已经使用过,其核心就是利用Redis中的setnx语句,该语句会判断在Redis中是否已经存在目标key,如果存在则操作失败,也就意味着获取锁失败。

image-20240310203311058

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.hmdp.utils;

import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

public class LockImpl implements ILock {
private String name;
private StringRedisTemplate stringRedisTemplate;
private static final String KEY_PREFIX = "lock:";
public LockImpl(String name,StringRedisTemplate stringRedisTemplate){
this.name = KEY_PREFIX + name;
this.stringRedisTemplate = stringRedisTemplate;
}

/**
* 能否获取锁
* @param timeout 超时时间
* @return
*/
public boolean tryLock(Long timeout){
String thread = Thread.currentThread().getName();
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(name, thread, timeout, TimeUnit.SECONDS);

return Boolean.TRUE.equals(success);
}

/**
* 释放锁
*/
public void unLock(){
stringRedisTemplate.delete(name);
}
}

通过本节需要掌握

  1. 使用Redis实现分布式锁
  2. 知道实现分布式锁的几种方案