在Redis 2.6.12之前,由于setnx 不支持设置过期时间,所以加锁的过期相对比较复杂,通常会为以下几步

  • 通过setnx获取锁,如果成功,再设置过期时间。 如果还没来得及执行expire操作,客户端就宕机了,将导致该锁永久有效,所以有下面这一步。
  • 如果客户端setnx获取锁失败,则检查一下该锁是否设置了过期时间,如果未设置过期时间则设置。这一步是对上面问题的弥补,如果某个创建锁的客户端在未执行expire时就宕机了,其它客户端可以通过修改key的过期时间,来保证锁最终会被Redis清除,自己有获取锁的机会。不过同样有一个问题,就是客户端之间的时间有可能会被覆盖,但实际业务中,通常这种覆盖是可以被接受的。如果情况特殊,可以用2.6.12之后的方式,关于这点在文末有代码介绍。
private final static String LOCK_PRE_FIX = "lock_";
	
	public boolean tryLock(String lockName, String id,int expretime,int timeout,) {
		long currentMillis = System.currentTimeMillis();
		long overMillis = currentMillis + timeout;
		final String fullLockName = LOCK_PRE_FIX + lockName;
		while(System.currentTimeMillis() < overMillis) {
			if(jedis.setnx(fullLockName, id) == 1) {
				jedis.expire(fullLockName, expretime);
				return true;
			}else if (jedis.ttl(fullLockName) == -1 ) {
				jedis.expire(fullLockName, expretime);
			}
			
			try {
				TimeUnit.MILLISECONDS.sleep(10);
			} catch (InterruptedException e) {
				//...
			}
		}
		return false;
	}

 而删除锁的过程有几种方式:一种通过LUA脚本的方式;一种通过客户端开事务来保证。

lua脚本删除代码如下:关于lua相关语法在其它博客中已经有过介绍,这里不多描述

private static final Long RELEASE_SUCCESS = 1L;
	
public boolean unLockByLua(String lockKey, String id) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(id));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
}

通过客户端事务的方式如下:获取锁的值,判断是不是自己创建的,如果是则删除。之所有要加事务,是因为在判断和del之间,锁有可能突然过期,然后其它线程突然获取到了锁,此时执行del操作就会把其它客户端创建的锁给释放掉。所以通常会用watch和事务来处理。如下

public void unLokc(String lockName,String id) {
		final String fullLockName = LOCK_PRE_FIX + lockName;
		jedis.watch(fullLockName);
		if(jedis.get(fullLockName).equals(id)) {
			Transaction multi = jedis.multi();
			try {
				multi.del(fullLockName);
				multi.exec();
			}finally {  
				multi.discard();
				jedis.unwatch();
			}
		}
	}

Redis2.6.12之后,加锁变得相当简单,原因在于Redis对set命令进行了扩展 

set key value [EX seconds] [PX milliseconds] [NX|XX]

代码如下,其中NX表示SET IF NOT EXIST,PX表示设置过期时间,值由expiretime指定

public static boolean tryLock(String lockName, String id, int expiretime) {
	String result = jedis.set(lockName, id, "NX", "PX", expiretime);
	if ("OK".equals(result)) {
	  return true;
	}
	return false;
}

释放锁的方式仍然源用上面两种,目前并没有更简化的方式,当然你可以客户端对功能封装,实用业务中调用简化。