Java使用Redis实现分布式锁

思路

主要用的是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);
            }
        }
    }

}



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


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值