Github 源码:
https://github.com/z521598/redis-lock
实现原理:
1.setnx
Redis 的 setnx 指令 (文档参考 http://www.redis.cn/commands/setnx.html),setnx 意为 SET if Not eXists, 命令格式: setnx $key $value
如果此 key 不存在, 则设置值为 value, 返回值为 1; 如果此 key 存在, 则不设置, 返回值为 0. 如下图:
- 127.0.0.1:6379[1]> setnx key v
- (integer) 1
- 127.0.0.1:6379[1]> setnx key v2
- (integer) 0
redis 是单线程的, 是线程安全的, setnx 指令由于上述的特性能够满足高并发情况下的对于锁的需求.
2.SpringData-redis
springData-redis 是向 Redis 发送命令以及接受数据的高层次抽象的模版方法, 简单理解为 "使用 java 向 redis 发送命令以及接受数据的客户端"(文档参考 https://docs.spring.io/spring-data/redis/docs/1.8.4.RELEASE/reference/html/ )
配置文件: https://github.com/z521598/redis-lock/blob/master/src/main/resources/applicationContext.xml
V1
说明:
最简单的最粗糙的锁实现, 实现了 2 个方法.
方法 1: 获取锁, public UUID acquire(String lockKey, long acquireTimeoutInMillis, long lockExpiryInMillis)
参数说明: lockKey 为锁的 key;acquireTimeoutInMillis 为获取锁的等待时间, 如果超过此时间就放弃锁; lockExpiryInMillis 为锁的过期时间.
返回值: 锁对应的值, 就是命令中 "setnx key value" 的 value.
思路:
1. 调用 SpringData-Redis 的 setIfAbsent(lockKey, value) 方法 (就是命令行中的 setnx),value 为随机的 UUID.
2. 如果返回 true, 则说明已经获取的锁, 则继续设置超时时间, 返回设置的 UUID.
如果返回 false, 则说明未获取到锁, 则休眠 100ms, 并记录总休眠的时间, 如果 lockExpiryInMillis 大于等于记录总休眠的时间, 则说明未获取到锁, 返回 null.
方法 2: 释放锁, public void release(String lockKey, UUID uuid)
参数说明: lockKey 为锁的 key;uuid 为锁的 value.
思路: 检查锁的 value 是否为 uuid, 如果相等, 则释放, 如果不相等, 则什么都不做; 防止释放了其他线程获取的锁.
明显的缺点:
第一步, 获取锁, 第二步, 然后设置超时时间. 这是 2 步操作, 不是原子性操作, 如果第一步操作之后, 程序崩溃了或者掉电了或者 redis 恰巧进行了主从切换等等原因, 第二步无法正常执行, 这样这个锁就永远得不到释放.
代码:
- public UUID acquire(String lockKey, long acquireTimeoutInMillis, long lockExpiryInMillis)
- throws InterruptedException {
- UUID uuid = UUID.randomUUID();
- long timeout = 0L;
- while (timeout < acquireTimeoutInMillis) {
- if (redisTemplate.opsForValue().setIfAbsent(lockKey, uuid.toString())) {
- redisTemplate.expire(lockKey, lockExpiryInMillis, TimeUnit.MILLISECONDS);
- return uuid;
- }
- TimeUnit.MILLISECONDS.sleep(DEFAULT_ACQUIRE_RESOLUTION_MILLIS);
- timeout += DEFAULT_ACQUIRE_RESOLUTION_MILLIS;
- }
- return null;
- }
锁实现: https://github.com/z521598/redis-lock/blob/master/src/main/java/com/redis/lock/sdata/v1/LockService.java
单元测试: https://github.com/z521598/redis-lock/blob/master/src/test/java/com/redis/lock/sdata/v1/LockServiceTest.java
V2
说明:
大体与 V1 相同, 但是锁的 value 是 "过期的时间", 如果获取锁的时候, 发现过期时间小于 now, 则视为锁已经过期.
缺点:(极少出现的情况)
当 redis 是主从形式的情况下, 获取锁之后, master 宕机, slave 接管, 但是这个时候 "新 master" 还未同步锁的 key. 在这个时候, 其他线程去获取锁, 发现无此 key, 则获取了不应该获取的锁, 这样就会引起不安全的情况.
代码:
锁实现: https://github.com/z521598/redis-lock/tree/master/src/main/java/com/redis/lock/sdata/v2
单元测试: https://github.com/z521598/redis-lock/blob/master/src/test/java/com/redis/lock/sdata/v2/LockV2ServiceTest.java
V3
敬请期待
来源: https://www.cnblogs.com/langshiquan/p/9531198.html