分布式锁总是一个热点讨论的话题,在涉及到多个系统调用,或者多台服务并行提供服务的情况下,对竞争资源的保护一般都会采用乐观锁,或分布式锁的方式实现,对单条资源的竞争一般选择乐观锁/悲观锁,而对过程的竞争往往是通过分布式锁的方式加以保护。实现分布式锁的方案也有很多。在实现分布式锁的过程中,往往有很多人会忽略一个细节,即只有加锁的人才具备释放锁的权利。这也是在公司中看一个”前人”在2015年实现的一个分布式锁源码,发现具有这个漏洞,特重新实现,以做记录。
话不多说,先上实现,这是一个基于redis实现的具备阻塞功能的,通用的分布式锁: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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126package org.zero.boot.util;
import java.util.Random;
import java.util.UUID;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.zero.boot.web.init.context.ApplicationContextHolder;
/**
* 加锁工具类
* @date 2018年1月23日 下午4:01:14
* @author zero
*/
public abstract class LockUtil {
// Redis操作api
public static RedisTemplate<?, ?> redisTemplate = (RedisTemplate<?, ?>) ApplicationContextHolder.application.getBean("redisTemplate");
private static final String LOCK_KEY_PREFIX = "lock:";
public static final long DEFAULT_LOCKING_TIME = 30_000L;
public static final long DEFAULT_WAITING_FOR_LOCK_TIME = 10_000L;
/**
* 获取默认配置的阻塞锁
* 阻塞容许时间 10s,取得锁时锁定时间 30s
* @param key
* @return
*/
public static boolean lock(String key) {
return lock(key, DEFAULT_WAITING_FOR_LOCK_TIME, DEFAULT_LOCKING_TIME);
}
/**
* 获取阻塞锁
* @param key 上锁资源
* @param waittimeout 阻塞时间
* @param lockingtime 锁定时间
* @return
*/
public static boolean lock(String key, long waittimeout, long lockingtime) {
String uuid = UUID.randomUUID().toString();
Random random = new Random();
final String lockKey = LOCK_KEY_PREFIX + key;
long timeoutpoing = System.currentTimeMillis() + waittimeout;
while(timeoutpoing > System.currentTimeMillis()) {
boolean setnx = redisTemplate.execute(new RedisCallback<Boolean>() {
"unchecked") (
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
byte[] skey = ((RedisSerializer<String>) redisTemplate.getKeySerializer()).serialize(lockKey);
byte[] svalue = ( (RedisSerializer<String>) redisTemplate.getValueSerializer()).serialize(uuid);
return connection.setNX(skey, svalue);
}
});
if(setnx) {
// 获取锁成功
return true;
}
try {
// 随机阻塞时间,提升锁竞争公平性,同时防止饥饿线程等待
Thread.sleep((long) (100 + random.nextInt(300)));
} catch (InterruptedException e) {
return false;
}
}
return false;
}
/**
* 释放锁
* @param key 锁定的资源
* @return
*/
public static boolean unlock(String key) {
String lockKey = LOCK_KEY_PREFIX + key;
if(uuid == null) {
return false;
}
try {
boolean flag = redisTemplate.execute(new RedisCallback<Boolean>() {
"unchecked") (
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
byte[] skey = ((RedisSerializer<String>) redisTemplate.getKeySerializer()).serialize(lockKey);
String lockuuid = ((RedisSerializer<String>) redisTemplate.getValueSerializer()).deserialize(connection.get(skey));
if(lockuuid == null) {
return Boolean.FALSE;
}
if(uuid.equals(lockuuid)) {
return connection.del(skey) > 0;
}
return Boolean.FALSE;
}
});
return flag;
} finally {
// 及时清理 ThreadLocalMap
LOCK_OWNER_UUID.remove();
}
}
/**
* 完全释放锁,不需要权限认证(即非本线程获取的锁也能释放)[慎用,最好别用]
* @param key 锁定的资源
* @return
*/
public static boolean superUnlock(String key) {
String lockKey = LOCK_KEY_PREFIX + key;
try {
boolean flag = redisTemplate.execute(new RedisCallback<Boolean>() {
"unchecked") (
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
byte[] skey = ((RedisSerializer<String>) redisTemplate.getKeySerializer()).serialize(lockKey);
return connection.del(skey) > 0;
}
});
return flag;
} finally {
}
}
}