Java本地锁(synchronized或J.U.C.Lock)只能解决当前jvm下的并发问题,如果是集群环境下或者一个机器跑多个jvm实例且相互间有交互或重叠时,此时需要一个“中央锁”来进行控制。
以下实现来自http://doc.redisfans.com/string/set.html相关描述:
命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。
客户端执行以上的命令:
如果服务器返回 OK ,那么这个客户端获得锁。
如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
设置的过期时间到达之后,锁将自动释放。
可以通过以下修改,让这个锁实现更健壮:
不使用固定的字符串作为键的值,而是设置一个不可猜测(non-guessable)的长随机字符串,作为口令串(token)。
不使用 DEL 命令来释放锁,而是发送一个 Lua 脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。
这两个改动可以防止持有过期锁的客户端误删现有锁的情况出现。
以下是一个简单的解锁脚本示例:
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
这个脚本可以通过 EVAL ...script... 1 resource-name token-value 命令来调用。
RedisLock.java
import java.util.UUID;
import redis.clients.jedis.Jedis;
/**
*
* A class that acts as a distributed lock using the method
* {@code Jedis.set(String key, String value, String nxxx, String expx, long time)}.
* <h3>Usage Examples</h3>
* <p>Here is an example:
* <pre>{@code //the time value 10 means the method invocation in try block takes no more than 10 seconds
* RedisLock lock = RedisLock.getLock("myLock",10);
* if(lock!=null){
* try{
* //doSomething
* } finally{
* lock.unlock();
* }
* }}
*/
public class RedisLock {
/**
* A value that identifies the current lock
*/
private final String value;
/**
* A key represents the lock
*/
private final String key;
/**
* Constructs a {@code RedisLock} used for release the lock via {@link #unlock()}
* @param key the key
* @param value the value, which identifies the current lock
*/
private RedisLock(String key, String value){
this.key = key;
this.value = value;
}
/**
* Try to hold the lock represented by the specified key and
* set a timeout(in seconds) on the lock (that is, the specified key)
* if the lock is successfully obtained to avoid the lock is never released
* when fails to invoke {@link #unlock()}(which means the method may be
* throws an uncaught exception, but not the return value {@code false}).
*
* @param key the key used as lock object
* @param seconds expire time in seconds
* @return {@code RedisLock} if the lock is successfully obtained, else {@code null}
* @throws NullPointerException if the specified key is null
* @throws IllegalArgumentException if the key is empty,
* or the time value is less then or equal to zero
*/
public static RedisLock getLock(String key, int seconds){
check(key, seconds);
String value = UUID.randomUUID().toString();
Jedis jedis = null;
try{
jedis = RedisPool.get();
//Redis 2.6.12 版本及以上 可以直接通过set命令实现
String status = jedis.set(key, value, "NX", "EX", seconds);
if("OK".equalsIgnoreCase(status)){
return new RedisLock(key, value);
}
//Redis 2.6.12 版本以前 可通过脚本实现
// String[] param = {key, value, String.valueOf(seconds)};
// String script = "local ok = redis.call('setnx', KEYS[1], ARGV[1]) if ok == 1 then redis.call('expire', KEYS[1], ARGV[2]) end return ok";
// Object status = jedis.eval(script, 1, param);
// if(((Number)status).intValue()==1){
// return new RedisLock(key, value);
// }
} finally{
RedisPool.close(jedis);
}
return null;
}
// 之所以不重载为getLock(String key, long millis)是因为int与long不好区分,想明确调用的话必须加后缀L
// 由于网络来回传输比较耗时,毫秒的精度是否有必要?
// /**
// * Try to hold the lock represented by the specified key and
// * set a timeout(in milliseconds) on the lock (that is, the specified key)
// * if the lock is successfully obtained to avoid the lock is never released
// * when fails to invoke {@link #unlock()}(which means the method may be
// * throws an uncaught exception, but not the return value {@code false}).
// *
// * @param key the key used as lock object
// * @param millis expire time in milliseconds
// * @return {@code RedisLock} If the lock is successfully obtained, else {@code null}
// * @throws NullPointerException if the specified key is null
// * @throws IllegalArgumentException if the key is empty,
// * or the time value is less then or equal to zero
// */
// public static RedisLock getLockMillis(String key, long millis){
// check(key, millis);
// String value = UUID.randomUUID().toString();
// Jedis jedis = null;
// try{
// jedis = RedisPool.get();
// //Redis 2.6.12 版本及以上 可以直接通过set命令实现
// String status = jedis.set(key, value, "NX", "PX", millis);
// if("OK".equalsIgnoreCase(status)){
// return new RedisLock(key, value);
// }
// //Redis 2.6.12 版本以前 可通过脚本实现
String[] param = {key, value, String.valueOf(millis)};
String script = "local ok = redis.call('setnx', KEYS[1], ARGV[1]) if ok == 1 then redis.call('PEXPIRE', KEYS[1], ARGV[2]) end return ok";
Object status = jedis.eval(script, 1, param);
if(((Number)status).intValue()==1){
return new RedisLock(key, value);
}
// } finally{
// RedisPool.close(jedis);
// }
//
// return null;
// }
private static void check(String key, long time){
checkKey(key);
if(time<=0){
throw new IllegalArgumentException("Invalid time value:"+time);
}
}
private static void checkKey(String key){
if(key==null){
throw new NullPointerException("Null key!");
}
if(key.isEmpty()){
throw new IllegalArgumentException("Empty key!");
}
}
/**
* Releases the lock.
* @return true if successfully releases by the current thread,
* false otherwise.
*/
public boolean unlock(){
Jedis jedis = null;
try{
jedis = RedisPool.get();
String[] param = {key, value};
Object status = jedis.eval("if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end", 1, param);
return ((Number)status).intValue()>0;
} finally{
RedisPool.close(jedis);
}
}
}
RedisPool.java
import redis.clients.jedis.Jedis;
/**
*
* 这里是你的Redis管理器的实现
*
*/
public final class RedisPool {
/**
* 从资源池里获取一个Jedis连接
* @return Jedis
*/
public static Jedis get(){
//TODO 作为示例 直接返回null
return null;
}
/**
* 将指定的Jedis连接归还给资源池
* @param jedis
*/
public static void close(Jedis jedis){
//TODO 你的实现
}
}