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

仅供个人记录

工具类:

RedisLock.java

import lombok.extern.slf4j.Slf4j;

/**
 * 分布式锁工具类.
 * @author Pete.Lee Aug 29, 2017 3:23:41 PM
 */
@Slf4j
public 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
 */
@Slf4j
public 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
@EnableCaching
public 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
 */
@Component
public 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 getBean(final String name, final Class 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();
      }
    }

你可能感兴趣的:(Java应用)