说完 Redis 分布式锁,面试官对我竖起大拇指

这个方案也有问题,因为获取锁和设置过期时间分成两步了,不是原子性操作,有可能获取锁成功但设置时间失败,那样不就白干了吗。

不过也不用急,这种事情 Redis 官方早为我们考虑到了,所以就引出了下面这个命令

2、SETEX,用法SETEX key seconds value

将值 value 关联到 key ,并将 key 的生存时间设为 seconds (以秒为单位)。如果 key 已经存在,SETEX 命令将覆写旧值。

这个命令类似于以下两个命令:

SET key value

EXPIRE key seconds  # 设置生存时间

这两步动作是原子性的,会在同一时间完成。

说完 Redis 分布式锁,面试官对我竖起大拇指

3、PSETEX ,用法PSETEX key milliseconds value

这个命令和SETEX命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像SETEX命令那样,以秒为单位。

不过,从 Redis 2.6.12 版本开始,SET命令可以通过参数来实现和SETNX、SETEX、PSETEX 三个命令的效果。

就比如这条命令

SET key value NX EX seconds

加上 NX、EX 参数后,效果就相当于 SETEX,这也是 Redis 获取锁写法里面最常见的。

怎么释放锁

=====

释放锁的命令就简单了,直接删除 key 就行,但我们前面说了,因为分布式锁必须由锁的持有者自己释放,所以我们必须先确保当前释放锁的线程是持有者,没问题了再删除,这样一来,就变成两个步骤了,似乎又违背了原子性了,怎么办呢?

不慌,我们可以用 lua 脚本把两步操作做拼装,就好像这样:

if Redis.call(“get”,KEYS[1]) == ARGV[1]

then

return Redis.call(“del”,KEYS[1])

else

return 0

end

KEYS[1]是当前 key 的名称,ARGV[1]可以是当前线程的 ID(或者其他不固定的值,能识别所属线程即可),这样就可以防止持有过期锁的线程,或者其他线程误删现有锁的情况出现。

代码实现

====

知道了原理后,我们就可以手写代码来实现 Redis 分布式锁的功能了,因为本文的目的主要是为了讲解原理,不是为了教大家怎么写分布式锁,所以我就用伪代码实现了。

首先是 Redis 锁的工具类,包含了加锁和解锁的基础方法:

public class RedisLockUtil {

private String LOCK_KEY = “redis_lock”;

// key的持有时间,5ms

private long EXPIRE_TIME = 5;

// 等待超时时间,1s

private long TIME_OUT = 1000;

// Redis命令参数,相当于nx和px的命令合集

private SetParams params = SetParams.setParams().nx().px(EXPIRE_TIME);

// Redis连接池,连的是本地的Redis客户端

JedisPool jedisPool = new JedisPool(“127.0.0.1”, 6379);

/**

* 加锁

* @param id

*            线程的id,或者其他可识别当前线程且不重复的字段

* @return

*/

public boolean lock(String id) {

Long start = System.currentTimeMillis();

Jedis jedis = jedisPool.getResource();

try {

for (;😉 {

// SET命令返回OK ,则证明获取锁成功

String lock = jedis.set(LOCK_KEY, id, params);

if (“OK”.equals(lock)) {

return true;

}

// 否则循环等待,在TIME_OUT时间内仍未获取到锁,则获取失败

long l = System.currentTimeMillis() - start;

if (l >= TIME_OUT) {

return false;

}

try {

// 休眠一会,不然反复执行循环会一直失败

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

} finally {

jedis.close();

}

}

/**

* 解锁

* @param id

*            线程的id,或者其他可识别当前线程且不重复的字段

* @return

*/

public boolean unlock(String id) {

Jedis jedis = jedisPool.getResource();

// 删除key的lua脚本

String script = “if Redis.call(‘get’,KEYS[1]) == ARGV[1] then” + "   return Redis.call(‘del’,KEYS[1]) " + “else”

+ "   return 0 " + “end”;

try {

String result =

jedis.eval(script, Collections.singletonList(LOCK_KEY), Collections.singletonList(id)).toString();

return “1”.equals(result);

} finally {

jedis.close();

}

}

}

具体的代码作用注释已经写得很清楚了,然后我们就可以写一个 demo 类来测试一下效果:

public class RedisLockTest {

private static RedisLockUtil demo = new RedisLockUtil();

private static Integer NUM = 101;

public static void main(String[] args) {

for (int i = 0; i < 100; i++) {

new Thread(() -> {

String id = Thread.currentThread().getId() + “”;

boolean isLock = demo.lock(id);

try {

// 拿到锁的话,就对共享参数减一

if (isLock) {

NUM–;

System.out.println(NUM);

}

} finally {

// 释放锁一定要注意放在finally

demo.unlock(id);

}

}).start();

}

}

}

我们创建 100 个线程来模拟并发的情况,执行后的结果是这样的:

说完 Redis 分布式锁,面试官对我竖起大拇指

最后

给大家送一个小福利

附高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。

}

我们创建 100 个线程来模拟并发的情况,执行后的结果是这样的:

说完 Redis 分布式锁,面试官对我竖起大拇指

最后

给大家送一个小福利

[外链图片转存中…(img-4RBwfxMX-1714800987379)]

附高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。

[外链图片转存中…(img-nwBHjzcz-1714800987380)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值