一个Redis分布式锁的工具类(升级版)

来源:互联网 发布:软件实施培训 编辑:程序博客网 时间:2024/06/06 00:41

仅供个人记录

工具类:

RedisLock.java

import lombok.extern.slf4j.Slf4j;/** * 分布式锁工具类. * @author Pete.Lee Aug 29, 2017 3:23:41 PM */@Slf4jpublic class RedisLock {  private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;  private final String lockKey;  /**   * 锁超时时间,防止线程在入锁以后,无限的执行等待   */  private int expireMsecs = 120 * 1000;  /**   * 锁等待时间,防止线程饥饿   */  private int timeoutMsecs = 20 * 1000;  private volatile Boolean locked = false;  private String myExpires = "";  public RedisLock(final String lockKey) {    this.lockKey = lockKey + "_lock";  }  public RedisLock(final String lockKey, final int timeoutMsecs) {    this(lockKey);    this.timeoutMsecs = timeoutMsecs;  }  public RedisLock(final String lockKey, final int timeoutMsecs, final int expireMsecs) {    this(lockKey, timeoutMsecs);    this.expireMsecs = expireMsecs;  }  public String getLockKey() {    return lockKey;  }  /**   * 获得 lock. 实现思路: 主要是使用了redis 的setnx命令,缓存了锁. reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间)   * 执行过程: 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值   * @return true if lock is acquired, false acquire timeouted   * @throws InterruptedException in case of thread interruption   */  public synchronized Boolean lock() throws InterruptedException {    int timeout = timeoutMsecs;    while (timeout >= 0) {      final long expires = System.currentTimeMillis() + expireMsecs + 1;      final String expiresStr = String.valueOf(expires); // 锁到期时间      if (RedisUtil.setNx(lockKey, expiresStr)) {        // lock acquired        myExpires = expiresStr;        locked = true;        return true;      }      final String currentValueStr = RedisUtil.get(lockKey); // redis里的时间      if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {        //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的        // lock is expired        final String oldValueStr = RedisUtil.getSet(lockKey, expiresStr);        //获取上一个锁到期时间,并设置现在的锁到期时间,        //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的        if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {          //防止误删(覆盖,因为key是相同的)了他人的锁——这里达不到效果,这里值会被覆盖,但是因为什么相差了很少的时间,所以可以接受          //[分布式的情况下]:如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁          // lock acquired          myExpires = expiresStr;          locked = true;          return true;        }      }      timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;      /*        延迟100 毫秒,  这里使用随机时间可能会好一点,可以防止饥饿进程的出现,即,当同时到达多个进程,        只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足.        使用随机的等待时间可以一定程度上保证公平性      */      Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);    }    return false;  }  public synchronized void unlock() {    // 如果当前redis中的锁与上锁相同删除锁    if (myExpires.equals(RedisUtil.get(lockKey))) {      if (locked) {        RedisUtil.del(lockKey);        locked = false;      }    }  }}

RedisUtil.java

import lombok.extern.slf4j.Slf4j;import org.springframework.data.redis.connection.RedisConnection;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.serializer.StringRedisSerializer;import java.util.ArrayList;import java.util.List;/** * redis 工具类. * @author Pete.Lee 2017/9/5 15:01 */@Slf4jpublic class RedisUtil {  private static StringRedisTemplate template = ContextUtil.getBean("StringRedisTemplate", StringRedisTemplate.class);  public static String get(final String key) {    String obj = null;    try {      obj = template.execute((final RedisConnection c) -> {        final StringRedisSerializer serializer = new StringRedisSerializer();        final byte[] data = c.get(serializer.serialize(key));        c.close();        return serializer.deserialize(data);      });    } catch (Exception ex) {      log.error("get redis error, key : {}", key);    }    return obj;  }  public static Boolean setNx(final String key, final String value) {    Boolean b = false;    try {      b = template.execute((final RedisConnection c) -> {        final StringRedisSerializer serializer = new StringRedisSerializer();        final Boolean success = c.setNX(serializer.serialize(key), serializer.serialize(value));        c.close();        return success;      });    } catch (Exception e) {      log.error("setNX redis error, key : {}", key);    }    return b;  }  public static String getSet(final String key, final String value) {    String obj = null;    try {      obj = template.execute((final RedisConnection c) -> {        final StringRedisSerializer serializer = new StringRedisSerializer();        final byte[] ret = c.getSet(serializer.serialize(key), serializer.serialize(value));        c.close();        return serializer.deserialize(ret);      });    } catch (Exception ex) {      log.error("setNX redis error, key : {}", key);    }    return obj;  }  public static Boolean del(final String key) {    Boolean obj = null;    try {      obj = template.execute((final RedisConnection c) -> {        final StringRedisSerializer serializer = new StringRedisSerializer();        return c.del(serializer.serialize(key)) > 0;      });    } catch (Exception ex) {      log.error("del redis error, key : {}", key);    }    return obj;  }  public static Long leftPush(final String key, final String value) {    return template.opsForList().leftPush(key, value);  }  public static Long rightPush(final String key, final String value) {    return template.opsForList().rightPush(key, value);  }  public static String leftPop(final String key) {    return template.opsForList().leftPop(key);  }  public static String rightPop(final String key) {    return template.opsForList().rightPop(key);  }  /*public RedisUtil() {    final JedisConnectionFactory factory = new JedisConnectionFactory();    factory.setHostName("xxxx");    factory.setPort(6379);    factory.setDatabase(2);    factory.afterPropertiesSet();    template = new StringRedisTemplate(factory);    template.afterPropertiesSet();    // System.out.println(template.boundValueOps("自增Key").increment(1));  }*/}

RedisConfigure.java

import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.datatype.joda.JodaModule;import org.springframework.cache.annotation.EnableCaching;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;@Configuration@EnableCachingpublic class RedisConfigure {  @Bean(name = "StringRedisTemplate")  public StringRedisTemplate template(final RedisConnectionFactory factory) {    final ObjectMapper om = new ObjectMapper();    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);    om.registerModule(new JodaModule());    final Jackson2JsonRedisSerializer<?> serializer = new Jackson2JsonRedisSerializer<>(Object.class);    serializer.setObjectMapper(om);    final StringRedisTemplate template = new StringRedisTemplate(factory);    template.setDefaultSerializer(serializer);    template.setKeySerializer(serializer);    template.setValueSerializer(serializer);    template.setHashValueSerializer(serializer);    template.afterPropertiesSet();    return template;  }}

ContextUtil.java

import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;/** * 获取Bean工具类. * @author Pete.Lee 2017/9/5 15:02 */@Componentpublic class ContextUtil implements ApplicationContextAware {  private static ApplicationContext context;  @Override  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {    ContextUtil.context = applicationContext;  }  public static ApplicationContext getContext() {    return context;  }  public static <T> T getBean(final String name, final Class<T> requiredType) {    return context.getBean(name, requiredType);  }}

YAML

spring:  redis:    host: 10.20.9.85    port: 6379    database: 0    password:    pool.max-idle: 8    pool.min-idle: 4    pool.max-active: 16    pool.max-wait: -1

应用

final RedisLock lock = new RedisLock("lottery_result");    while (true) {      try {        if (lock.lock()) {          // 需要加锁的代码        }      } catch (InterruptedException ex) {      } finally {        lock.unlock();      }    }
原创粉丝点击