spring-data-redis
模块包含RedisAtomicLong
类。
在本课程中,您可以看到
public boolean compareAndSet(long expect, long update) {
return generalOps.execute(new SessionCallback<Boolean>() {
@Override
@SuppressWarnings("unchecked")
public Boolean execute(RedisOperations operations) {
for (;;) {
operations.watch(Collections.singleton(key));
if (expect == get()) {
generalOps.multi();
set(update);
if (operations.exec() != null) {
return true;
}
}
{
return false;
}
}
}
});
}
我的问题是为什么它起作用?
generalOps.multi()
在调用get()
之后开始事务。这意味着有可能两个不同的线程(甚至客户端)可以更改值,并且两个都将成功。
operations.watch
是否以某种方式阻止了它? JavaDoc没有解释此方法的目的。
PS:次要问题:为什么for (;;)
?总是有一个迭代。
答案 0 :(得分:1)
operations.watch是否以某种方式阻止了它?
是。查看密钥后,如果在事务完成之前已对密钥进行了修改,则EXEC
将失败。因此,如果EXEC
成功,则其他人可以保证该值保持不变。
为什么要使用(;;)?总是有一个迭代。
在您的情况下,似乎无限循环是多余的。
但是,如果要执行检查并设置操作以用旧值修改值,则需要无限循环。从redis doc中查看以下示例:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
由于EXEC
可能会失败,因此您需要循环重试整个过程,直到成功为止。
答案 1 :(得分:1)
问:Operations.watch是否以某种方式阻止了它?
是。
引用Redis documentation about transaction:
WATCH用于为Redis事务提供检查并设置(CAS)行为。
监视已监视的键,以便检测对它们的更改。如果在EXEC命令之前至少修改了一个监视键,则整个事务将中止,并且EXEC返回Null答复以通知该事务失败。
您可以从该文档中了解有关Redis交易的更多信息。
问:为什么要使用(;;)?总是有一个迭代。
您发布的代码似乎很旧。从Google的this url缓存中,我看到了您提供的代码可以追溯到Oct 15th, 2012
!
最新代码看起来有很大不同:
答案 2 :(得分:0)
RedisAtomicLong.compareAndSet
的实现并非最佳,因为它需要 5个Redis请求
Redisson-Redis Java客户端提供了更有效的实现。
org.redisson.RedissonAtomicLong#compareAndSetAsync
方法是使用原子EVAL脚本实现的:
"local currValue = redis.call('get', KEYS[1]); "
+ "if currValue == ARGV[1] "
+ "or (tonumber(ARGV[1]) == 0 and currValue == false) then "
+ "redis.call('set', KEYS[1], ARGV[2]); "
+ "return 1 "
+ "else "
+ "return 0 "
+ "end",
此脚本仅需要对Redis的单个请求。
用法示例:
RAtomicLong atomicLong = redisson.getAtomicLong("myAtomicLong");
atomicLong.compareAndSet(1L, 2L);