基于Redis的可重入锁,这个版本通过spring注入方式完成的Service版本,有时间写一个Util的版本。
1.定义的Lock接口,与JDK内置的类似,添加了过期时间,没有支持Condition。
import java.util.concurrent.TimeUnit;
/**
* @author : wudi28
* @date : 2018/9/24 18:35
*/
public interface IDistributedLockService {
/**
* 尝试获取锁(自旋)
* @param key 锁对应的key
* @param timeout 等待锁的超时时间
* @param unit 等待锁的超时时间单位
* @param expireSecs 锁的过期时间(秒)
* */
boolean tryLock(String key, long timeout, TimeUnit unit, long expireSecs);
/**
* 尝试获取锁,仅一次,获取不到直接返回
* @param key 锁对应的key
* @param expireSecs 锁的过期时间(秒)
* */
boolean tryLock(String key, long expireSecs);
/**
* 阻塞获取锁
* @param key 锁对应的key
* @param expireSecs 锁的过期时间(秒)
* */
void lock(String key, long expireSecs);
/**
* 释放锁
* @param key 锁对应的key
* */
void unlock(String key);
}
2.实现类,需要使用方替换RedisService,实现其中的set,get,del方法,一般大家都会有专门的Redis工具类或者原生
2.1同步实现的方式没有使用阻塞的方式,使用简单的自旋+CAS,缺点是占CPU, 再有就是非公平的(不是FIFO)
2.2可以换成Redis中的BLPOP来实现阻塞waiting,当持有锁线程释放锁时,RPUSH一条数据,相当于notify。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @author : wudi28
* @date : 2018/9/24 18:51
*/
@Service
public class RedisReentrantDistributedLockService implements IDistributedLockService{
@Resource
private RedisService redisService;
private static final Logger LOGGER = LoggerFactory.getLogger(RedisReentrantDistributedLockService.class);
/**分布式线程的唯一标识,使用本机IP+UUID形式,释放锁后会删除,防止线程池方式的重入锁*/
private static ThreadLocal<String> TOKEN_MAP = new ThreadLocal<>();
/**本机IP*/
private static String IP;
/**Redis成功响应码*/
private static final String OK = "OK";
/**Redis中的key前缀*/
private static final String LOCK_PREFIX = "LOCK_PREFIX_";
/**最大等待秒数*/
private static final long FOREVER = Long.MAX_VALUE;
private static final String METHOD_DESC = "Redis分布式锁";
static {
try {
IP = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
LOGGER.error("分布式Redis锁,获取IP异常", e);
IP = "UNKNOWN_HOST";
}
}
@Override
public boolean tryLock(String key, long time, TimeUnit unit, long expireSecs) {
if (tryLock(key, expireSecs)) {
LOGGER.debug(METHOD_DESC +",直接tryLock成功|key={}|token={}", key, getToken());
return true;
}
boolean held = false;
long start = System.nanoTime();
//自旋CAS,获取不到锁 && 没有超时
while (!held && (System.nanoTime() - start) < unit.toNanos(time)) {
held = getLock(key, expireSecs);
}
if (!held) {
LOGGER.debug(METHOD_DESC +",tryLock超时|key={}|token={}", key, getToken());
} else {
LOGGER.debug(METHOD_DESC +",自旋tryLock成功|key={}|token={}", key, getToken());
}
return held;
}
@Override
public boolean tryLock(String key, long expireSecs) {
//尝试获取锁
if (getLock(key, expireSecs)) {
return true;
}
//如果设置token失败,查询缓存中的token是否与自己的相同,如果相同返回true,即重入
return getToken().equals(getLockToken(key));
}
@Override
public void lock(String key, long expireSecs) {
tryLock(key, FOREVER, TimeUnit.SECONDS, expireSecs);
}
@Override
public void unlock(String key) {
releaseLock(key);
TOKEN_MAP.remove();
LOGGER.debug(METHOD_DESC +",释放锁成功|key={}|token={}", key, getToken());
}
private String getToken(){
String threadToken = TOKEN_MAP.get();
if (null == threadToken || threadToken.length() < 1) {
TOKEN_MAP.set(threadToken = IP + "_" + UUID.randomUUID().toString().replaceAll("-", ""));
}
return threadToken;
}
/**
* 使用redis的原子方法set,设置当前线程的token,设置成功即获取到锁
*
* */
private boolean getLock(String key, long expireSecs) {
//NX表示只有对应的key不存在时,才设置, XX表示只有key存在时,才设置
//EX表示设置的超时时间单位为秒 PX为毫秒
return OK.equals(redisService.set(keyOf(key), getToken(), "NX", "EX", expireSecs));
}
/**
* 获取当前持有锁的token
* */
private String getLockToken(String key) {
return redisService.get(keyOf(key));
}
/**
* 释放锁,即删除对应的key
* */
private void releaseLock(String key) {
redisService.del(keyOf(key));
}
/**
* key+前缀
* */
private String keyOf(String key) {
return LOCK_PREFIX + key;
}
}
3.没有经过大量测试,有使用问题,下方留言
public class Test{
@Resource
private IDistributedLockService iDistributedLockService;
private static ExecutorService queryThreadPool = Executors.newCachedThreadPool();
@Test
public void testLock() throws ExecutionException, InterruptedException {
List<Future> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
futures.add(queryThreadPool.submit(new LockThread(iDistributedLockService)));
}
for (Future future : futures) {
future.get();
}
/*
输出:
pool-1-thread-1,开始
pool-1-thread-3,开始
pool-1-thread-2,开始
pool-1-thread-5,开始
pool-1-thread-4,开始
pool-1-thread-3,获取锁成功
pool-1-thread-3,重入锁成功
pool-1-thread-3,释放锁成功
pool-1-thread-1,获取锁成功
pool-1-thread-1,重入锁成功
pool-1-thread-1,释放锁成功
pool-1-thread-2,获取锁成功
pool-1-thread-2,重入锁成功
pool-1-thread-2,释放锁成功
pool-1-thread-5,获取锁成功
pool-1-thread-5,重入锁成功
pool-1-thread-5,释放锁成功
pool-1-thread-4,获取锁成功
pool-1-thread-4,重入锁成功
pool-1-thread-4,释放锁成功*/
}
public static class LockThread implements Runnable {
private IDistributedLockService lockService;
public LockThread(IDistributedLockService lockService) {
this.lockService = lockService;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",开始");
lockService.lock("TEST_KEY", 10);
System.out.println(Thread.currentThread().getName() + ",获取锁成功");
lockService.lock("TEST_KEY", 10);
System.out.println(Thread.currentThread().getName() + ",重入锁成功");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
lockService.unlock("TEST_KEY");
System.out.println(Thread.currentThread().getName() + ",释放锁成功");
}
}
}