Java使用Redis实现分布式锁

来源:互联网 发布:知乎怎么关注话题 编辑:程序博客网 时间:2024/06/05 05:12

思路

主要用的是redis的setnx()函数,利用函数的特性,即成功setnx的话返回1,如果key已经存在,setnx返回0,来判断能否将key插入到redis中。

但是要考虑锁无法正常释放的情况,避免成为垃圾锁,导致其他进程或线程无法进入加锁区域,需要特殊处理。通过redis的expire()函数来给key设置过期时间,当各种原因未能对锁unlock时,可以通过过期时间来实现锁的释放。

代码

package com.jikefriend.test.common.jedis;import org.apache.commons.lang.StringUtils;import org.apache.commons.lang.math.RandomUtils;import redis.clients.jedis.Jedis;public class RedisLock {    /** 毫秒与毫微秒的换算单位 1毫秒 = 1000000毫微秒 */    private static final long MILLI_NANO_CONVERSION = 1000 * 1000L;    private static final String EXPIRE_KEY_PREFIX = "lock:%s";    private boolean locked;    private String key;    public RedisLock(String key) {        if (StringUtils.isEmpty(key)) {            throw new IllegalArgumentException("Param key can not be null or empty");        }        this.key = String.format(EXPIRE_KEY_PREFIX, key);        this.locked = false;    }    /**     * 获取锁     * 在timeout时间内会一直尝试获取锁,如果timeout=0,则表示获取失败后直接返回不再尝试     * 锁的有效期expireSecs必须大于0,当超过有效期未被unlock时,系统将会强制释放     * @param timeout     获取锁的等待时间     * @param expireSecs  锁的有效时间,必须大于0     * @return     */    public boolean lock(long timeout, int expireSecs) {        if (expireSecs <= 0) {            throw new IllegalArgumentException("Param expireSecs must lager than zero.");        }        long nano = System.nanoTime();        timeout *= MILLI_NANO_CONVERSION;        String lockStart = String.valueOf(System.currentTimeMillis());        Jedis jedis = JedisUtil.getResource();        try {            while ((System.nanoTime() - nano) < timeout) {                if (jedis.setnx(this.key, lockStart) == 1) {                    jedis.expire(this.key, expireSecs);                    this.locked = true;                    return this.locked;                }                Thread.sleep(3, RandomUtils.nextInt(500));            }            String expireStr = jedis.get(key);            Long now = System.currentTimeMillis();            String nowStr = String.valueOf(now);            if (!StringUtils.isNumeric(expireStr))            {                return false;            }            //ttl小于0 表示key上没有设置生存时间(key是不会不存在的,因为前面setnx会自动创建)            //出现这种状况,是因为某个实例setnx成功后,expire由于各种可能原因而没有被调用造成的            //这时可以直接设置expire来占有锁            long ttlValue = jedis.ttl(this.key);            if(ttlValue<0){                jedis.setnx(key, nowStr);                jedis.expire(key, expireSecs);                return true;            }            Long expireLong = Long.parseLong(expireStr);            if (now - expireLong > expireSecs * 1000)            {                jedis.del(key);                jedis.setnx(key, nowStr);                jedis.expire(key, expireSecs);                return true;            }        } catch (Exception e) {            if (jedis != null) {                JedisUtil.returnBrokenResource(jedis);            }            throw new RuntimeException("Locking error", e);        } finally {            if (jedis != null) {                JedisUtil.returnResource(jedis);            }        }        return false;    }    /**     * 释放锁     * @return     */    public boolean unLock() {        Jedis jedis = JedisUtil.getResource();        try {            if (jedis.del(this.key) == 1) {                this.locked = false;                return true;            }            return false;        } catch (Exception e) {            if (jedis != null) {                JedisUtil.returnBrokenResource(jedis);            }            throw new RuntimeException("UnLocking error", e);        } finally {            if (jedis != null) {                JedisUtil.returnResource(jedis);            }        }    }}



如有考虑不妥的地方,还请大神指正。


0 0
原创粉丝点击