当前java开发最火热的开发方式应该都是使用springboot来进行开发,springboot的优点这里就不做过多叙述
这里使用springboot RedisTemplate实现分布式锁;
上代码前先说明,redis实现分布式锁,是利用redis唯一key的特性,唯一key创建时如果成功会返回1(true),已经存在无法创建会返回0(false),
另redis存在事件通知订阅功能,利用该功能可以监听key删除或过期来唤醒等待锁的线程及时竞争锁(该键空间通知功能需要在redis中开启,否则不会生效)
可以利用这个特性来进行实现分布式锁;废话不多说,直接上代码
编写RedisUtils用于封装redis工具,方便外部调用
package com.miyou.demo.utils.lock.impl;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import java.util.AbstractQueue;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class RedisUtils implements MessageListener {
private final Map<String, AbstractQueue<MessageListener>> messageMaps = new HashMap<>();
private RedisTemplate<String, Object> template;
/**
* retemplate相关配置
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(redisConnectionFactory);
// 值采用json序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 设置hash key 和value序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
/**
* redis事件监听
* @param redisConnectionFactory
* @param redisUtils
* @return
*/
@Bean
public RedisMessageListenerContainer keyEventMessageListener(RedisConnectionFactory redisConnectionFactory, RedisUtils redisUtils) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
container.addMessageListener((message, bytes) -> {
log.info("删除key----->{}",message.toString());
redisUtils.onMessage(message, bytes);
},new PatternTopic("__keyspace@*__:del"));
container.addMessageListener((message, bytes) -> {
log.info("key----->{},已过期",message.toString());
redisUtils.onMessage(message, bytes);
},new PatternTopic("__keyevent@*__:expired"));
return container;
}
/**
* redis唯一key
* @return 保存成功/保存失败
*/
public Boolean setIfAbsent(String key,Object value,long timeOut){
return template.opsForValue()
.setIfAbsent(key,value,timeOut, TimeUnit.MILLISECONDS);
}
public Object get(String key){
return template.opsForValue().get(key);
}
/**
* 删除缓存
* @param key 键
*/
public Boolean del(String key){
return template.delete(key);
}
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(毫秒)
*/
public void expire(String key, long time){
template.expire(key, time, TimeUnit.MILLISECONDS);
}
@Override
public void onMessage(Message message, byte[] bytes) {
AbstractQueue<MessageListener> listeners = messageMaps.get(message.toString());
if(listeners != null){
listeners.forEach(s->s.onMessage(message, bytes));
}
}
/**
* 将事件通知回调方法存入队列
* @param key 键
* @param messageListener 回调对象
*/
public void addMessageValueMap(String key,MessageListener messageListener){
if(!messageMaps.containsKey(key)) {
synchronized (messageMaps) {
if(!messageMaps.containsKey(key)) {
messageMaps.put(key,new ConcurrentLinkedQueue<>());
}
}
}
messageMaps.get(key).add(messageListener);
}
/**
* 删除事件通知回调方法
* @param key 键
* @param messageListener 回调对象
*/
public void removeMessageValue(String key,MessageListener messageListener){
messageMaps.get(key).remove(messageListener);
}
}
定义Lock接口,声明锁的基本功能
package com.miyou.demo.utils.lock;
/**
* 锁接口类
*/
public interface Lock {
/**
* 获取锁
*/
void lock();
/**
* 尝试获取锁
*
* @return
*/
boolean tryLock() ;
/**
* 在指定时间内尝试获取锁
* @param timeout 毫秒
* @return
*/
boolean tryLock(long timeout);
/**
* 释放锁
*/
void unlock();
}
编写RedisLock实现分布式锁
package com.miyou.demo.utils.lock.impl;
import com.miyou.demo.utils.lock.Lock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.MessageListener;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* redis分布式锁
* @author lrx
*/
@Slf4j
public class RedisLock implements Lock {
/**
* 将当前线程锁基本信息存入ThreadLocal
*/
private ThreadLocal<RedisLockEntity> localEntity = ThreadLocal.withInitial(RedisLockEntity::new);
private final static String ROOT_PATH = "REDIS_LOCK";
private RedisUtils redisUtils;
private String key;
private long timeout = 20000;
/**
* 创建锁对象
* @param key 锁key
*/
public RedisLock(String key, RedisUtils redisUtils){
this.redisUtils = redisUtils;
this.key = ROOT_PATH+":"+key;
}
/**
* 创建锁对象
* @param key 锁key
* @param timeout 锁超时时间(耗秒)
*/
public RedisLock(String key, long timeout, RedisUtils redisUtils){
this(key,redisUtils);
this.timeout = timeout;
//如果指定了超时时间则默认不续期
this.localEntity.get().setRenewFlg(false);
}
@Override
public void lock(){
while (!tryLock()) {
this.await(-1);
}
}
@Override
public boolean tryLock() {
RedisLockEntity entity = localEntity.get();
//没有拿到锁时才去获取锁
if(!entity.isLock()) {
entity.setLock(redisUtils.setIfAbsent(key,entity.getLockValue(),timeout));
//如果拿到锁了则启动自动续期
if(entity.isLock()) {
this.renew();
}
}
return entity.isLock();
}
@Override
public boolean tryLock(long timeout) {
boolean state;
long startTime = System.currentTimeMillis();
long waitTime = 0;
for(;;){
state = tryLock();
if(state || waitTime >= timeout){break;}//如果拿到锁,或等待时间使用完则停止自旋
this.await(timeout);
waitTime = System.currentTimeMillis()-startTime;
}
return state;
}
@Override
public void unlock() {
RedisLockEntity entity = localEntity.get();
entity.setRenewFlg(false);//停止续期
//只能释放自己的锁
if(entity.isLock() && entity.getLockValue().equals(redisUtils.get(key))){
entity.setLock(false);
redisUtils.del(key);
}else {
log.error("线程:{}未获取到锁无法释放!!",Thread.currentThread().getId());
throw new RuntimeException("The current thread has not acquired the lock");
}
}
/**
* 监听事件通知
*/
private void await(long timeout){
CountDownLatch downLatch = new CountDownLatch(1);
MessageListener messageListener = (message, bytes) -> downLatch.countDown();
redisUtils.addMessageValueMap(key,messageListener);
try {//因redis事件通知不可靠,如果没有指定等待时间则只等待30秒
downLatch.await(timeout>0?timeout:30000,TimeUnit.MILLISECONDS);
} catch (InterruptedException ignored) {}
redisUtils.removeMessageValue(key,messageListener);
}
/**
* 自动续期
*/
private void renew(){
RedisLockEntity entity = localEntity.get();
if(entity.isRenewFlg() && timeout > 10000){
new Thread( () -> {
//如果启动了自动续期,且是自己的锁,则循环续期
while (entity.isRenewFlg() && entity.getLockValue().equals(redisUtils.get(key))){
try {
Thread.sleep(timeout-10000);
} catch (InterruptedException ignored) {}
redisUtils.expire(key, timeout);
log.info("{}-->自动续期完成!",key);
}
}).start();
}
}
static class RedisLockEntity{
private boolean lock = false;
private String lockValue = UUID.randomUUID().toString();
private boolean renewFlg = true;
public void setLock(boolean lock) {
this.lock = lock;
}
public boolean isLock() {
return lock;
}
public void setRenewFlg(boolean renewFlg){
this.renewFlg = renewFlg;
}
public boolean isRenewFlg() {
return renewFlg;
}
public String getLockValue() {
return lockValue;
}
}
}
以上为redis分布式锁的基本实现原理和思路,当然现在已经有基于redis实现分布式锁的组件redisson,该组件redis锁接口继承了java Lock接口,因此在使用时可以像java Lock接口一样使用更加简单方便,下篇记录redisson组件redis分布式锁的使用