您的位置:首页 > 数据库 > Redis

分布式锁--Redis实现

2016-02-25 15:39 513 查看
实现原理:Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。

SETNX命令(SET if Not eXists) 语法: SETNX key value 功能: 当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

直接上代码:

public class RedisLockService {

/** 加锁标志 */
public static final String LOCKED = "TRUE";

/** 毫秒与纳秒的换算单位 1毫秒 = 1000000纳秒 */
public static final long MILLI_NANO_CONVERSION = 1000 * 1000L;

/** 默认超时时间(毫秒) */
public static final long DEFAULT_TIME_OUT = 500;

public static final Random RANDOM = new Random();

/** 锁的超时时间(秒),过期删除 */
public static final int EXPIRE = 10;

private Logger logger = Logger.getLogger(RedisLockService.class);

//private String key;

// 锁状态标志
//private boolean locked = false;

@Autowired
private PriceCacheService cacheService;

/**
* 加锁
* 应该以:
* lock();
* try {
*      doSomething();
* } finally {
*      unlock();
* }
* 的方式调用
* @param timeout 超时时间
* @return 成功或失败标志
*/
public boolean lock(String key, long timeout) {
long nano = System.nanoTime();
timeout *= MILLI_NANO_CONVERSION;
try {
while ((System.nanoTime() - nano) < timeout) {
//if (this.jedis.setnx(this.key, LOCKED) == 1) {
//this.jedis.expire(this.key, EXPIRE);
if (cacheService.setNX(key, LOCKED, EXPIRE)) { //cacheService对Redis进行了简单的封装
//this.locked = true;
//return this.locked;
return true;
}
// 短暂休眠,避免出现活锁
Thread.sleep(3, RANDOM.nextInt(500));
}
} catch (Exception e) {
throw new RuntimeException("Locking error", e);
}
return false;
}
/**
* 加锁
* 应该以:
* lock();
* try {
*      doSomething();
* } finally {
*      unlock();
* }
* 的方式调用
* @param timeout 超时时间
* @param expire 锁的超时时间(秒),过期删除
* @return 成功或失败标志
*/
public boolean lock(String key, long timeout, int expire) {
long nano = System.nanoTime();
timeout *= MILLI_NANO_CONVERSION;
try {
while ((System.nanoTime() - nano) < timeout) {
//if (this.jedis.setnx(this.key, LOCKED) == 1) {
//	this.jedis.expire(this.key, expire);
if (cacheService.setNX(key, LOCKED, expire)) {
//this.locked = true;
//return this.locked;
return true;
}
// 短暂休眠,避免出现活锁
Thread.sleep(3, RANDOM.nextInt(500));
}
} catch (Exception e) {
throw new RuntimeException("Locking error", e);
}
return false;
}
/**
* 加锁
* 应该以:
* lock();
* try {
*      doSomething();
* } finally {
*      unlock();
* }
* 的方式调用
* @return 成功或失败标志
*/
public boolean lock(String key) {
return lock(key, DEFAULT_TIME_OUT);
}
/**
* 解锁
* 无论是否加锁成功,都需要调用unlock
* 应该以:
* lock();
* try {
*      doSomething();
* } finally {
*      unlock();
* }
* 的方式调用
*/
public void unlock(String key) {
try {
//if (this.locked) {
//this.jedis.del(this.key);
cacheService.delNX(key);
//this.locked = false;
//}
} catch(Exception e){
throw new RuntimeException("Locking error", e);
}
}
}


上面的lock方法中会不断尝试的去获取锁,直到超时。可以对该方法进行改造:

public boolean acquire(String key){
try{
long expires = System.currentTimeMillis() + EXPIRE*1000  + 1;
String expiresStr = String.valueOf(expires); //锁到期时间

if (cacheService.setNX(key, expiresStr)) {    //设置key对应的value为该key的过期时间
// lock acquired
return true;
}

String currentValueStr = (String)cacheService.getReal(key); //redis里的时间
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
//判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
// lock is expired

String oldValueStr = cacheService.getSet(key, expiresStr);
//获取上一个锁到期时间,并设置现在的锁到期时间,
//只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
//如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
// lock acquired
return true;
}
}
return false;
}catch(Throwable t){
logger.error("[RedisLockService] acquire ERROR, key:"+key+", "+t.getMessage(), t );
return false;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  分布式锁 Redis