Java分布式锁的三种实现方案(redis)

摘要:
乐观数据库锁乐观锁通常基于数据版本记录机制实现。有一个字段(left_count)记录剩余的礼品数量。在并发情况下如何确保左计数不是负数。乐观锁是通过向红色数据包表中添加版本号字段(version)来实现的。该键设置过期时间。GETSET命令语法:当键存在但不是字符串类型时,将返回与该键关联的字符串值。如果键不存在,则返回特殊值nil。

方案一:数据库乐观锁

乐观锁通常实现基于数据版本(version)的记录机制实现的,比如有一张红包表(t_bonus),有一个字段(left_count)记录礼物的剩余个数,用户每领取一个奖品,对应的left_count减1,在并发的情况下如何要保证left_count不为负数,乐观锁的实现方式为在红包表上添加一个版本号字段(version),默认为0。

异常实现流程

1
2
3
4
5
6
7
8
9
10
11
12
-- 可能会发生的异常情况
-- 线程1查询,当前left_count为1,则有记录
select * from t_bonus where id = 10001and left_count > 0
 
-- 线程2查询,当前left_count为1,也有记录
select * from t_bonus where id = 10001and left_count > 0
 
-- 线程1完成领取记录,修改left_count为0,
update t_bonus set left_count = left_count - 1where id = 10001
 
-- 线程2完成领取记录,修改left_count为-1,产生脏数据
update t_bonus set left_count = left_count - 1where id = 10001

通过乐观锁实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 添加版本号控制字段
ALTER TABLE table ADD COLUMN version INT DEFAULT '0'NOT NULL AFTER t_bonus;
 
-- 线程1查询,当前left_count为1,则有记录,当前版本号为1234
select left_count, version from t_bonus where id = 10001and left_count > 0
 
-- 线程2查询,当前left_count为1,有记录,当前版本号为1234
select left_count, version from t_bonus where id = 10001and left_count > 0
 
-- 线程1,更新完成后当前的version为1235,update状态为1,更新成功
update t_bonus set version = 1235, left_count = left_count-1where id = 10001and version = 1234
 
-- 线程2,更新由于当前的version为1235,udpate状态为0,更新失败,再针对相关业务做异常处理
update t_bonus set version = 1235, left_count = left_count-1where id = 10001and version = 1234

方案二:基于Redis的分布式锁

SETNX命令(SET if Not eXists)
语法:SETNX key value
功能:原子性操作,当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。
Expire命令
语法:expire(key, expireTime)
功能:key设置过期时间
GETSET命令
语法:GETSET key value
功能:将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。
GET命令
语法:GET key
功能:返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。
DEL命令
语法:DEL key [KEY …]
功能:删除给定的一个或多个 key ,不存在的 key 会被忽略。

第一种:使用redis的setnx()、expire()方法,用于分布式锁

  • setnx(lockkey, 1) 如果返回0,则说明占位失败;如果返回1,则说明占位成功
  • expire()命令对lockkey设置超时时间,为的是避免死锁问题。
  • 执行完业务代码后,可以通过delete命令删除key。

这个方案其实是可以解决日常工作中的需求的,但从技术方案的探讨上来说,可能还有一些可以完善的地方。比如,如果在第一步setnx执行成功后,在expire()命令执行成功前,发生了宕机的现象,那么就依然会出现死锁的问题

第二种:使用redis的setnx()、get()、getset()方法,用于分布式锁,解决死锁问题

  • setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2。
  • get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向3。
  • 计算newExpireTime=当前时间+过期超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。
  • 判断currentExpireTime与oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。
  • 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行delete释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。
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
importcn.com.tpig.cache.redis.RedisService;
importcn.com.tpig.utils.SpringUtils;
/**
* Created by IDEA
* User: shma1664
* Date: 2016-08-16 14:01
* Desc: redis分布式锁
*/
publicfinalclassRedisLockUtil {
privatestaticfinalintdefaultExpire = 60;
privateRedisLockUtil() {
//
}
/**
* 加锁
* @param key redis key
* @param expire 过期时间,单位秒
* @return true:加锁成功,false,加锁失败
*/
publicstaticbooleanlock(String key, intexpire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
longstatus = redisService.setnx(key, "1");
 
if(status == 1) {
redisService.expire(key, expire);
returntrue;
}
returnfalse;
}
publicstaticbooleanlock(String key) {
returnlock2(key, defaultExpire);
}
/**
* 加锁
* @param key redis key
* @param expire 过期时间,单位秒
* @return true:加锁成功,false,加锁失败
*/
publicstaticbooleanlock2(String key, intexpire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
longvalue = System.currentTimeMillis() + expire;
longstatus = redisService.setnx(key, String.valueOf(value));
if(status == 1) {
returntrue;
}
longoldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime < System.currentTimeMillis()) {
//超时
longnewExpireTime = System.currentTimeMillis() + expire;
longcurrentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));
if(currentExpireTime == oldExpireTime) {
returntrue;
}
}
returnfalse;
}
publicstaticvoidunLock1(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
redisService.del(key);
}
publicstaticvoidunLock2(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
longoldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime > System.currentTimeMillis()) {
redisService.del(key);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
publicvoiddrawRedPacket(longuserId) {
String key = "draw.redpacket.userid:"+ userId;
booleanlock = RedisLockUtil.lock2(key, 60);
if(lock) {
try{
//领取操作
} finally{
//释放锁
RedisLockUtil.unLock(key);
}
} else{
newRuntimeException("重复领取奖励");
}
}

Spring AOP基于注解方式和SpEL实现开箱即用的redis分布式锁策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
/**
* RUNTIME
* 定义注解
* 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。
* @author shma1664
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public@interfaceRedisLockable {
String[] key() default"";
longexpiration() default60;
}
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
importjavax.annotation.Resource;
importjava.lang.reflect.Method;
importcom.autohome.api.dealer.util.cache.RedisClient;
importcom.google.common.base.Joiner;
importorg.aspectj.lang.ProceedingJoinPoint;
importorg.aspectj.lang.Signature;
importorg.aspectj.lang.annotation.Around;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.Pointcut;
importorg.aspectj.lang.reflect.MethodSignature;
importorg.springframework.expression.EvaluationContext;
importorg.springframework.expression.Expression;
importorg.springframework.expression.ExpressionParser;
importorg.springframework.expression.spel.standard.SpelExpressionParser;
importorg.springframework.expression.spel.support.StandardEvaluationContext;
importorg.springframework.stereotype.Component;
/**
* Created by IDEA
* User: mashaohua
* Date: 2016-09-28 18:08
* Desc:
*/
@Aspect
@Component
publicclassRedisLockAop {
@Resource
privateRedisClient redisClient;
@Pointcut("execution(* com.autohome.api.dealer.tuan.service.*.*(..))")
publicvoidpointcut(){}
@Around("pointcut()")
publicObject doAround(ProceedingJoinPoint point) throwsThrowable{
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
String targetName = point.getTarget().getClass().getName();
String methodName = point.getSignature().getName();
Object[] arguments = point.getArgs();
if(method != null&& method.isAnnotationPresent(RedisLockable.class)) {
RedisLockable redisLock = method.getAnnotation(RedisLockable.class);
longexpire = redisLock.expiration();
String redisKey = getLockKey(targetName, methodName, redisLock.key(), arguments);
booleanisLock = RedisLockUtil.lock2(redisKey, expire);
if(!isLock) {
try{
returnpoint.proceed();
} finally{
unLock2(redisKey);
}
} else{
thrownewRuntimeException("您的操作太频繁,请稍后再试");
}
}
returnpoint.proceed();
}
privateString getLockKey(String targetName, String methodName, String[] keys, Object[] arguments) {
StringBuilder sb = newStringBuilder();
sb.append("lock.").append(targetName).append(".").append(methodName);
if(keys != null) {
String keyStr = Joiner.on(".").skipNulls().join(keys);
String[] parameters = ReflectParamNames.getNames(targetName, methodName);
ExpressionParser parser = newSpelExpressionParser();
Expression expression = parser.parseExpression(keyStr);
EvaluationContext context = newStandardEvaluationContext();
intlength = parameters.length;
if(length > 0) {
for(inti = 0; i < length; i++) {
context.setVariable(parameters[i], arguments[i]);
}
}
String keysValue = expression.getValue(context, String.class);
sb.append("#").append(keysValue);
}
returnsb.toString();
}
1
2
3
4
5
6
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.18.1-GA</version>
</dependency>
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
importjavassist.*;
importjavassist.bytecode.CodeAttribute;
importjavassist.bytecode.LocalVariableAttribute;
importjavassist.bytecode.MethodInfo;
importorg.apache.log4j.Logger;
/**
* Created by IDEA
* User: mashaohua
* Date: 2016-09-28 18:39
* Desc:
*/
publicclassReflectParamNames {
privatestaticLogger log = Logger.getLogger(ReflectParamNames.class);
privatestaticClassPool pool = ClassPool.getDefault();
static{
ClassClassPath classPath = newClassClassPath(ReflectParamNames.class);
pool.insertClassPath(classPath);
}
publicstaticString[] getNames(String className,String methodName) {
CtClass cc = null;
try{
cc = pool.get(className);
CtMethod cm = cc.getDeclaredMethod(methodName);
// 使用javaassist的反射方法获取方法的参数名
MethodInfo methodInfo = cm.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
if(attr == null) returnnewString[0];
intbegin = 0;
String[] paramNames = newString[cm.getParameterTypes().length];
intcount = 0;
intpos = Modifier.isStatic(cm.getModifiers()) ? 0: 1;
for(inti = 0; i < attr.tableLength(); i++){
// 为什么 加这个判断,发现在windows 跟linux执行时,参数顺序不一致,通过观察,实际的参数是从this后面开始的
if(attr.variableName(i).equals("this")){
begin = i;
break;
}
}
for(inti = begin+1; i <= begin+paramNames.length; i++){
paramNames[count] = attr.variableName(i);
count++;
}
returnparamNames;
} catch(Exception e) {
e.printStackTrace();
}finally{
try{
if(cc != null) cc.detach();
} catch(Exception e2) {
log.error(e2.getMessage());
}
}
returnnewString[0];
}
}

在需要使用分布式锁的地方添加注解

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 抽奖接口
* 添加redis分布式锁保证一个订单只有一个请求处理,防止用户刷礼物,支持SpEL表达式
* redisLockKey:lock.com.autohome.api.dealer.tuan.service.impl.drawBonus#orderId
* @param orderId 订单id
* @return 抽中的奖品信息
*/
@RedisLockable(key = {"#orderId"}, expiration = 120)
@Override
publicBonusConvertBean drawBonus(Integer orderId) throwsBonusException{
// 业务逻辑
}

第三种方案:基于Zookeeper的分布式锁

利用节点名称的唯一性来实现独占锁

ZooKeeper机制规定同一个目录下只能有一个唯一的文件名,zookeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建/lock/${lock_name}_lock节点,最终成功创建的那个客户端也即拥有了这把锁,创建失败的可以选择监听继续等待,还是放弃抛出异常实现独占锁。
package com.shma.example.zookeeper.lock;

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
importjava.io.IOException;
importjava.util.ArrayList;
importjava.util.Collections;
importjava.util.List;
importjava.util.concurrent.CountDownLatch;
importjava.util.concurrent.TimeUnit;
importjava.util.concurrent.locks.Condition;
importjava.util.concurrent.locks.Lock;
importorg.apache.zookeeper.*;
importorg.apache.zookeeper.data.Stat;
/**
* Created by IDEA
* User: mashaohua
* Date: 2016-09-30 16:09
* Desc:
*/
publicclassZookeeperLock implementsLock, Watcher {
privateZooKeeper zk;
privateString root = "/locks";//根
privateString lockName;//竞争资源的标志
privateString myZnode;//当前锁
privateintsessionTimeout = 30000;
privateList<Exception> exception = newArrayList<Exception>();
/**
* 创建分布式锁,使用前请确认config配置的zookeeper服务可用
* @param config 127.0.0.1:2181
* @param lockName 竞争资源标志,lockName中不能包含单词lock
*/
publicZookeeperLock(String config, String lockName){
this.lockName = lockName;
// 创建一个与服务器的连接
try{
zk = newZooKeeper(config, sessionTimeout, this);
Stat stat = zk.exists(root, false);
if(stat == null){
// 创建根节点
zk.create(root, newbyte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch(IOException e) {
exception.add(e);
} catch(KeeperException e) {
exception.add(e);
} catch(InterruptedException e) {
exception.add(e);
}
}
@Override
publicvoidlock() {
if(exception.size() > 0){
thrownewLockException(exception.get(0));
}
if(!tryLock()) {
thrownewLockException("您的操作太频繁,请稍后再试");
}
}
@Override
publicvoidlockInterruptibly() throwsInterruptedException {
this.lock();
}
@Override
publicbooleantryLock() {
try{
myZnode = zk.create(root + "/"+ lockName, newbyte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
returntrue;
} catch(KeeperException e) {
e.printStackTrace();
} catch(InterruptedException e) {
e.printStackTrace();
}
returnfalse;
}
@Override
publicbooleantryLock(longtime, TimeUnit unit) throwsInterruptedException {
returntryLock();
}
@Override
publicvoidunlock() {
try{
zk.delete(myZnode, -1);
myZnode = null;
zk.close();
} catch(InterruptedException e) {
e.printStackTrace();
} catch(KeeperException e) {
e.printStackTrace();
}
}
@Override
publicCondition newCondition() {
returnnull;
}
@Override
publicvoidprocess(WatchedEvent watchedEvent) {
//
}
}
1
2
3
4
5
6
7
8
9
10
11
ZookeeperLock lock = null;
try{
lock = newZookeeperLock("127.0.0.1:2182","test1");
lock.lock();
//业务逻辑处理
} catch(LockException e) {
throwe;
} finally{
if(lock != null)
lock.unlock();
}

利用临时顺序节点控制时序实现

/lock已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。

算法思路:对于加锁操作,可以让所有客户端都去/lock目录下创建临时顺序节点,如果创建的客户端发现自身创建节点序列号是/lock/目录下最小的节点,则获得锁。否则,监视比自己创建节点的序列号小的节点(比自己创建的节点小的最大节点),进入等待。

对于解锁操作,只需要将自身创建的节点删除即可。

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
packagecom.shma.example.zookeeper.lock;
importjava.io.IOException;
importjava.util.ArrayList;
importjava.util.Collections;
importjava.util.List;
importjava.util.concurrent.CountDownLatch;
importjava.util.concurrent.TimeUnit;
importjava.util.concurrent.locks.Condition;
importjava.util.concurrent.locks.Lock;
importorg.apache.zookeeper.CreateMode;
importorg.apache.zookeeper.KeeperException;
importorg.apache.zookeeper.WatchedEvent;
importorg.apache.zookeeper.Watcher;
importorg.apache.zookeeper.ZooDefs;
importorg.apache.zookeeper.ZooKeeper;
importorg.apache.zookeeper.data.Stat;
/**
* Created by IDEA
* User: mashaohua
* Date: 2016-09-30 16:09
* Desc:
*/
publicclassDistributedLock implementsLock, Watcher{
privateZooKeeper zk;
privateString root = "/locks";//根
privateString lockName;//竞争资源的标志
privateString waitNode;//等待前一个锁
privateString myZnode;//当前锁
privateCountDownLatch latch;//计数器
privateintsessionTimeout = 30000;
privateList<Exception> exception = newArrayList<Exception>();
/**
* 创建分布式锁,使用前请确认config配置的zookeeper服务可用
* @param config 127.0.0.1:2181
* @param lockName 竞争资源标志,lockName中不能包含单词lock
*/
publicDistributedLock(String config, String lockName){
this.lockName = lockName;
// 创建一个与服务器的连接
try{
zk = newZooKeeper(config, sessionTimeout, this);
Stat stat = zk.exists(root, false);
if(stat == null){
// 创建根节点
zk.create(root, newbyte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
} catch(IOException e) {
exception.add(e);
} catch(KeeperException e) {
exception.add(e);
} catch(InterruptedException e) {
exception.add(e);
}
}
/**
* zookeeper节点的监视器
*/
publicvoidprocess(WatchedEvent event) {
if(this.latch != null) {
this.latch.countDown();
}
}
publicvoidlock() {
if(exception.size() > 0){
thrownewLockException(exception.get(0));
}
try{
if(this.tryLock()){
System.out.println("Thread "+ Thread.currentThread().getId() + " "+myZnode + " get lock true");
return;
}
else{
waitForLock(waitNode, sessionTimeout);//等待锁
}
} catch(KeeperException e) {
thrownewLockException(e);
} catch(InterruptedException e) {
thrownewLockException(e);
}
}
publicbooleantryLock() {
try{
String splitStr = "_lock_";
if(lockName.contains(splitStr))
thrownewLockException("lockName can not contains \u000B");
//创建临时子节点
myZnode = zk.create(root + "/"+ lockName + splitStr, newbyte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(myZnode + " is created ");
//取出所有子节点
List<String> subNodes = zk.getChildren(root, false);
//取出所有lockName的锁
List<String> lockObjNodes = newArrayList<String>();
for(String node : subNodes) {
String _node = node.split(splitStr)[0];
if(_node.equals(lockName)){
lockObjNodes.add(node);
}
}
Collections.sort(lockObjNodes);
System.out.println(myZnode + "=="+ lockObjNodes.get(0));
if(myZnode.equals(root+"/"+lockObjNodes.get(0))){
//如果是最小的节点,则表示取得锁
returntrue;
}
//如果不是最小的节点,找到比自己小1的节点
String subMyZnode = myZnode.substring(myZnode.lastIndexOf("/") + 1);
waitNode = lockObjNodes.get(Collections.binarySearch(lockObjNodes, subMyZnode) - 1);
} catch(KeeperException e) {
thrownewLockException(e);
} catch(InterruptedException e) {
thrownewLockException(e);
}
returnfalse;
}
publicbooleantryLock(longtime, TimeUnit unit) {
try{
if(this.tryLock()){
returntrue;
}
returnwaitForLock(waitNode,time);
} catch(Exception e) {
e.printStackTrace();
}
returnfalse;
}
privatebooleanwaitForLock(String lower, longwaitTime) throwsInterruptedException, KeeperException {
Stat stat = zk.exists(root + "/"+ lower,true);
//判断比自己小一个数的节点是否存在,如果不存在则无需等待锁,同时注册监听
if(stat != null){
System.out.println("Thread "+ Thread.currentThread().getId() + " waiting for "+ root + "/"+ lower);
this.latch = newCountDownLatch(1);
this.latch.await(waitTime, TimeUnit.MILLISECONDS);
this.latch = null;
}
returntrue;
}
publicvoidunlock() {
try{
System.out.println("unlock "+ myZnode);
zk.delete(myZnode,-1);
myZnode = null;
zk.close();
} catch(InterruptedException e) {
e.printStackTrace();
} catch(KeeperException e) {
e.printStackTrace();
}
}
publicvoidlockInterruptibly() throwsInterruptedException {
this.lock();
}
publicCondition newCondition() {
returnnull;
}
publicclassLockException extendsRuntimeException {
privatestaticfinallongserialVersionUID = 1L;
publicLockException(String e){
super(e);
}
publicLockException(Exception e){
super(e);
}
}
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持脚本之家!

免责声明:文章转载自《Java分布式锁的三种实现方案(redis)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇JSONP Hijackin攻击详解Spring boot validation校验下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

分布式数据库中的事务时序

概述 在单机数据库领域,我们为每个事务都分配一个序列号,比如Oracle的SCN(SystemChangeNumber),MySQL的LSN(LogSequenceNumber),这个序列号可以是逻辑的,也可以是物理的。我们依赖这个序列号对系统中发生的事务进行排序,确保所有事务都有严格的先后关系。数据库中所有的事务都按分配的序列号排序,对于任何时间点发生的...

Android刷机教程

我的机器是Nexus 5 一. 安装驱动 如何进入fastboot模式 1. 拔掉数据线,将手机关机 2. 关机后同时按住【音量减小键】和【开关机键】即可进入Fastboot模式 开启usb调试  --> 勾选usb调试 adb devices可以看到设备名即可 二. 下载刷机包 这是Android 4.4的刷机包,大家可...

NTFS隐写

------------恢复内容开始------------ 总结下以前做题遇到的有趣的知识点, Buuctf里的杂项漂流的马里奥,中NTFS隐写到NTFS在渗透过程的妙用。 NTFS交换数据流(Alternate DataStreams,简称ADS)是NTFS磁盘格式的一个特性,在NTFS文件系统下,每个文件都可以存在多个数据流。通俗的理解,就是其它文件...

js实现之--防抖节流【理解+代码】

防抖:     理解:在车站上车,人员上满了车才发走重点是人员上满触发一次。     场景:实时搜索,拖拽。     实现:         //每一次都要清空定时器,重新设置上计时器值,使得计时器每一次都重新开始,直到最后满足条件并且等待delay时间后,才开始执行handler函数。 // func是用户传入需要防抖的函数 // wait是等待时间 c...

前端请求参数MD5加密发送后台

最近在项目开发中遇到前端发送参数加密的问题,网上查找半天也是很乱,小编自己在项目开发中总结了一下,写到博客中,希望能够帮助大家。 贴上html,javascript代码 1 <!doctype html> 2 <html lang="en"> 3 <head> 4 <meta charset="UT...

Redis 监视器

一  介绍 客户端可以通过执行MONITOR命令,将客户端转换成监视器,接收并打印服务器处理的每个命令请求的相关信息。 当一个客户端从普通客户端变为监视器时,该客户端的REDIS_MONITOR标识会被打开。服务器将所有监视器都记录在monitors链表中。每次处理命令请求时,服务器都会遍历monitors链表,将相关信息发送给监视器。 当前客户端变身监...