package com.sqi;
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.params.SetParams;
import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException;
/** * @author 麒 * @version 1.0 * @since 2019-03-22 */ public class RedisLockUtil { private static final String LOCK_SUCCESS = "OK"; private JedisPool jedisPool; private final Map<String, Boolean> DAEMON_MAP = new ConcurrentHashMap<String, Boolean>(); private static final String REENTRANT_LOCK_SCRIPT = "local value = redis.call('get',KEYS[1]) " + "if value then " + " if value == ARGV[1] then " + " return redis.call('pexpire', KEYS[1], ARGV[2]) " + " else " + " return 0 " + " end " + "else " + " redis.call('set',KEYS[1],ARGV[1],'NX','PX',ARGV[2]) " + " return 1 " + "end"; private static final String WAIT_LOCK_SCRIPT = "local value = redis.call('get',KEYS[1]) " + "if value then " + " if value == ARGV[1] then " + " return redis.call('pexpire', KEYS[1], ARGV[2]) " + " else " + " return redis.call('pttl', KEYS[1]) " + " end " + "else " + " redis.call('set',KEYS[1],ARGV[1],'NX','PX',ARGV[2]) " + " return 1 " + "end"; private static final String REFRESH_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('pexpire', KEYS[1],ARGV[2]) " + "else " + " return 0 " + "end"; private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end";
1public RedisLockUtil(JedisPool jedisPool) { 2 this.jedisPool = jedisPool; 3} 4 5protected Jedis getJedis() { 6 return jedisPool.getResource(); 7} 8 9/\*\* 10 \* 获取redis分布式锁,只要锁存在就返回false 11 \* 12 \* @param lockKey 锁的名字 13 \* @param lockOwner 锁的所有者,通常为当前请求的requestID 14 \* @param expire 锁过期时间单位毫秒 15 \* @return 16 \*/ 17public boolean getSimpleRedisLock(String lockKey, String lockOwner, Long expire) { 18 SetParams setParams = SetParams.setParams().nx().px(expire); 19 String result = getJedis().set(lockKey, lockOwner, setParams); 20 return result != null ? LOCK\_SUCCESS.equals(result) : false; 21} 22 23/\*\* 24 \* 获取redis分布式锁, 如果锁存在且属于当前申请者,则刷新锁过期时间,返回true 25 \* 26 \* @param lockKey 锁的名字 27 \* @param lockOwner 锁的所有者,通常为当前请求的requestID 28 \* @param expire 锁过期时间单位毫秒 29 \* @return 30 \*/ 31public boolean getReentrantRedisLock(String lockKey, String lockOwner, Long expire) { 32 List keyList = Collections.singletonList(lockKey); 33 List argvList = Arrays.asList(lockOwner,expire.toString()); 34 Long result = (Long) getJedis().eval(REENTRANT\_LOCK\_SCRIPT, keyList, argvList); 35 return result > 0; 36} 37 38 39/\*\* 40 \* 获取Redis锁,如果锁被占用则阻塞 41 \* 42 \* @param lockKey 锁的名字 43 \* @param lockOwner 锁的所有者,通常为当前请求的requestID 44 \* @param expire 锁过期时间单位毫秒 45 \* @param timeOut 超时时间毫秒 46 \* @return 47 \* @throws InterruptedException 48 \* @throws TimeoutException 49 \*/ 50public boolean waitRedisLock(String lockKey, String lockOwner, Long expire, Long timeOut) throws InterruptedException, TimeoutException { 51 long startTime = System.currentTimeMillis(); 52 List keyList = Collections.singletonList(lockKey); 53 List argvList = Arrays.asList(lockOwner,expire.toString()); 54 while (true) { 55 long nowTime = System.currentTimeMillis(); 56 if (timeOut > 0 && (nowTime - startTime) >= timeOut) { 57 throw new TimeoutException(); 58 } 59 try { 60 Long result = (Long) getJedis().eval(WAIT\_LOCK\_SCRIPT, keyList, argvList); 61 if (result == 1) { 62 return true; 63 } 64 Thread.sleep(result); 65 } catch (Exception e) { 66 Thread.sleep((timeOut >> 8) + 10); 67 } 68 } 69} 70 71/\*\* 72 \* 刷新锁过期时间 73 \* 74 \* @param lockKey 75 \* @param lockOwner 76 \* @param expire 77 \*/ 78public boolean refreshLockExpire(String lockKey, String lockOwner, Long expire) { 79 List keyList = Collections.singletonList(lockKey); 80 List argvList = Arrays.asList(lockOwner,expire.toString()); 81 return (Long) getJedis().eval(REFRESH\_LOCK\_SCRIPT, keyList, argvList) > 0; 82} 83 84/\*\* 85 \* 释放锁 86 \* 87 \* @param lockKey 88 \* @param lockOwner 89 \* @return 90 \*/ 91public boolean releaseLock(String lockKey, String lockOwner) { 92 Long result = (Long) getJedis().eval(RELEASE\_LOCK\_SCRIPT, Collections.singletonList(lockKey), Collections.singletonList(lockOwner)); 93 if (result > 0) { 94 DAEMON\_MAP.remove(lockKey); 95 return true; 96 } 97 return false; 98} 99 100/\*\* 101 \* 获取Redis锁,如果锁被占用则阻塞,启动一个守护线程用来保证在任务进行中锁不过期 102 \* 103 \* @param lockKey 锁的名字 104 \* @param lockOwner 锁的所有者,通常为当前请求的requestID 105 \* @param expire 锁过期时间单位毫秒 106 \* @return 107 \*/ 108public boolean waitRedisLockWithDaemon(final String lockKey, final String lockOwner, final Long expire, final long timeOut) throws TimeoutException, InterruptedException { 109 boolean sig = waitRedisLock(lockKey, lockOwner, expire, timeOut); 110 if (sig) { 111 DAEMON\_MAP.put(lockKey, true); 112 lockDaemon(lockKey, lockOwner, expire); 113 } 114 return sig; 115} 116 117/\*\* 118 \* 获取redis分布式锁,启动一个守护线程用来保证在任务进行中锁不过期 119 \* 120 \* @param lockKey 锁的名字 121 \* @param lockOwner 锁的所有者,通常为当前请求的requestID 122 \* @param expire 锁过期时间单位毫秒 123 \* @return 124 \*/ 125public boolean getRedisLockWithDaemon(final String lockKey, final String lockOwner, final Long expire) { 126 boolean sig = getSimpleRedisLock(lockKey, lockOwner, expire); 127 if (sig) { 128 DAEMON\_MAP.put(lockKey, true); 129 lockDaemon(lockKey, lockOwner, expire); 130 } 131 return sig; 132} 133 134/\*\* 135 \* 锁的守护线程,保证在锁持有期内不会过期 136 \* 137 \* @param lockKey 138 \* @param lockOwner 139 \* @param expire 140 \*/ 141protected void lockDaemon(final String lockKey, final String lockOwner, final Long expire) { 142 long exp; 143 if (expire > 1000) { 144 exp = expire >> 1; 145 } else { 146 exp = expire; 147 } 148 final long finalExp = exp; 149 Thread thread = new Thread(new Runnable() { 150 public void run() { 151 Boolean sig = DAEMON\_MAP.get(lockKey); 152 if (sig == null) { 153 sig = false; 154 } 155 try { 156 while (sig) { 157 if (refreshLockExpire(lockKey, lockOwner, expire)) { 158 Thread.sleep(finalExp); 159 sig = DAEMON\_MAP.get(lockKey); 160 } else { 161 sig = false; 162 DAEMON\_MAP.remove(lockKey); 163 } 164 } 165 } catch (Exception e) { 166 throw new RuntimeException(e); 167 } 168 } 169 }); 170 thread.setDaemon(true); 171 thread.start(); 172}
}