Redis实现分布式锁

一. 概述

在java开发中, 多线程环境下为了保证一个代码块在同一时间只能由一个线程访问, 我们一般可以使用synchronized语法和ReetrantLock去保证,这实际上是本地锁的方式。但是现在公司都是流行分布式架构,在分布式环境下,如何保证不同节点的线程同步执行呢?今天我们来介绍利用Redis实现分布式锁

二. Redis分布式锁简单实用

2.1 导入Redisson依赖包

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.8.2</version>
</dependency>

2.2 使用

Config config = new Config();
config.useClusterServers()
    .setScanInterval(2000) // cluster state scan interval in milliseconds
    .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
    .addNodeAddress("redis://127.0.0.1:7002");
// 配置redis地址
RedissonClient redisson = Redisson.create(config);
// 获取实例
RLock lock = redisson.getLock("anyLock");
// 获取锁
lock.lock();

try {
    // 执行业务代码块
    ...
} finally {
    // 释放锁
    lock.unlock();
}

三. 自己实现一个简单的redis锁

public class RedisLockUtil {
    private Logger log = LoggerFactory.getLogger(RedisDao.class);

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // 当前线程唯一常量
    private ThreadLocal<String> threadKeyId = ThreadLocal.withInitial(() -> UUID.randomUUID().toString());

    /**
     * redis获取锁,获取不到就会不断重试
     * @param key  锁名
     * @param time  锁的最大有效时间(秒)
     */
    public void lock(String key,int time){
        // 锁名
        String keyName = getLockName(key);
        while(true){
            // 争抢锁
            Boolean flag = setnxAndExpire(keyName, threadKeyId.get(), time);
            // 判断key是否存在
            if(!flag){
                // 判断是否重入锁
                if(threadKeyId.get().equals(String.valueOf(stringRedisTemplate.opsForValue().get(keyName)))){
                    break;
                }
                // 短暂休眠,nano避免出现活锁
                try {
                    Thread.sleep(500, 500);
                } catch (InterruptedException e) {
                    break;
                }
            }else {
                break;
            }
        }
    }

    /**
     * 尝试获取锁,立即返回结果
     * @param key
     * @param time
     * @return
     */
    public Boolean tryLock(String key,int time){
        // 锁名
        String keyName = getLockName(key);
        return setnxAndExpire(keyName, threadKeyId.get(), time);
    }

    /**
     * 释放锁
     * @param key
     */
    public void unlock(String key){
        // 锁名
        String keyName = getLockName(key);
        // 释放锁
        if (threadKeyId.get().equals(stringRedisTemplate.opsForValue().get(keyName))) {
            stringRedisTemplate.delete(keyName);
        }
    }

    /**
     * 获取锁的名字
     * @param key
     * @return
     */
    public String getLockName(String key){
        if(null != key){
            return "REDIS_LOCK_"+key;
        }else {
            return "DEFAULT_LOCK";
        }
    }
    
    /**
     * key不存在时设置value,一般用于争抢锁
     * 把setnx和expire合并,保证原子性,
     * @param key
     * @param value 值
     * @param time 秒
     * @return
     */
    public Boolean setnxAndExpire(String key ,String value,long time){
        /**
         * set key value [EX seconds] [PX milliseconds] [NX|XX]
         * EX seconds:设置失效时长,单位秒
         * PX milliseconds:设置失效时长,单位毫秒
         * NX:key不存在时设置value,成功返回OK,失败返回(nil)
         * XX:key存在时设置value,成功返回OK,失败返回(nil)
         *
         * 示例: set name p7+ ex 100 nx
         * SET操作成功后,返回的是OK,失败返回NIL
         */

        return stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
            /**
             * 给定命令与给定参数一起的“本机”或“原始”执行。 该命令按原样执行,并且尽可能少地执行“解释”,这取决于调用方对参数或结果的任何处理
             */
            Object obj = connection.execute("set", SafeEncoder.encode(key), SafeEncoder.encode(value), SafeEncoder.encode("EX"), Protocol.toByteArray(time), SafeEncoder.encode("NX"));
            return obj != null;
        });
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容