RedisLock.java 4.46 KB
package com.uccc.base.springboot.redisLock;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

/**
 * Redis Lock:redis锁(注意:必须在事务外使用,否则解锁-事务提交之间,可能被其他线程lock,导致脏读)
 */
public class RedisLock {
	

	private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
	
    RedisTemplate<String, String> redis;

	private String[] keys;
	private int expireSeconds; // 过期时间
	private int retrySeconds; // 重试时间
	private long timeValue = 0; // lock设置的时间戳:0 失败;>0 时间戳
	private int retryMillis;

	private String lockPrefix;
	private List<String> lockKeys; // 已加锁的key列表

	public RedisLock(RedisTemplate<String, String> redis,int expireSeconds,int retrySeconds,String lockPrefix,int retryMillis,String... keys) {
		if(keys==null||keys.length==0)
		{
			throw new RuntimeException("redisLock keys can't be empty");
		}
		this.keys = keys;
		this.expireSeconds = expireSeconds;
		this.retrySeconds = retrySeconds;
		this.lockPrefix = lockPrefix;
		this.lockKeys = new LinkedList<String>();
		this.redis = redis;
		this.retryMillis = retryMillis;
	}

	public RedisLock(String prefix, Object... objs) {
		this.keys = new String[objs.length];
		for (int i = 0; i < objs.length; i++) {
			keys[i] = prefix + objs[i];
		}
	}


	/**
	 * 排序自旋锁:setnx && expire
	 */
	public boolean acquire() {
		ValueOperations<String, String> operations = redis.opsForValue();
		try {
			Arrays.sort(keys);
			lockKeys.clear();
			long now = System.currentTimeMillis();
			// 避免死锁:value = 当前时间 + 过期时间 + 1毫秒
			this.timeValue = now + expireSeconds * 1000 + 1;
			while (true) {
				for (String key : keys) {
					String lockKey = lockPrefix + key;
					if (lockKeys.contains(lockKey))
						continue;
					boolean result = operations.setIfAbsent(lockKey, String.valueOf(timeValue));
					if (result) {
						// 使用expire,如果代码运行至此,redis连接崩溃,该key没有设置超时时间 => 死锁
						redis.expire(lockKey, expireSeconds, TimeUnit.SECONDS);
						lockKeys.add(lockKey);
					}
					// 解决死锁:判断key对应的value是否超时
					else {
						String currentValueStr = operations.get(lockKey);
						
//						String currentValueStr = jedis.get(lockKey);
						if (currentValueStr != null && System.currentTimeMillis() > Long.valueOf(currentValueStr)) {
							// 并发竞争:getset取出旧值再判断一次,避免被其他线程set
							String oldValueStr = operations.getAndSet(lockKey, String.valueOf(timeValue));
							if (oldValueStr == null || oldValueStr.equals(currentValueStr)) {
								lockKeys.add(lockKey);
								continue;
							}
						}
						break;
					}
				}
				// 全部锁住
				if (lockKeys.size() == keys.length) {
					return true;
				}
				// 超时未成功:释放加的锁
				else if (System.currentTimeMillis() - now > retrySeconds * 1000) {
					if (lockKeys.size() > 0) {
						redis.delete(lockKeys);
						lockKeys.clear();
					}
					return false;
				}

				// TODO 当前线程将持有jedis一段时间(retrySeconds),如果并发数过大,可能会导致jedis连接数不足
				Thread.sleep(retryMillis);
			}
		} catch (Throwable e) {
			logger.warn("获取redis lock异常", e);
			if (lockKeys.size() > 0) {
				redis.delete(lockKeys);
				lockKeys.clear();
			}
			return false;
		}
	}

	/**
	 * 解锁:删除 key(从redis线程池未获取到jedis => 解锁失败)
	 */
	public void release() {
		ValueOperations<String, String> operations = redis.opsForValue();
		try {
			for (String lockKey : lockKeys) {
				String valueStr = operations.get(lockKey);
				// timeValue不等:可能发生当前线程lock后,业务操作比较耗时,导致超时后被其他线程lock
				if (valueStr != null && timeValue == Long.parseLong(valueStr))
					redis.delete(lockKey);
			}
			lockKeys.clear();
		} catch (Exception e) {
			logger.warn("释放redis lock异常", e);
		} finally {
			this.timeValue = 0;
		}
	}
}