Java结合Redis实现分布式可重入阻塞独占锁

思路就是Redis解决进程间的竞争,Java解决同进程内线程的竞争,这样,可以达到一个减少Redis访问次数的一个效果。

阻塞到锁释放,使用Redis无法实现,所以使用Java的阻塞机制。轮询sleep会造成不必要的资源浪费,所以使用synchronized锁的wait来实现阻塞,再通过Redis的发布订阅来监听到锁删除时,唤醒阻塞线程。

以上就是大题思路,然后上代码

public class RedisLock extends JedisPubSub {
    private static final JedisPool JEDIS_POOL;
    // 存储重入次数
    private static final ThreadLocal<Map<String, Integer>> LOCK_REENTRY_COUNT = new ThreadLocal<>();
    // 存储key对应锁
    private static final ConcurrentMap<String, Object> LOCK_MAP = new ConcurrentHashMap<>();

    static {
        GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxTotal(1000);
        JEDIS_POOL = new JedisPool(poolConfig, "127.0.0.1", 6379);

        // 开启一个线程监听锁的key删除或过期
        new Thread(() -> {
            try (Jedis jedis = JEDIS_POOL.getResource()) {
                jedis.psubscribe(new RedisLock(), "__keyevent*:expired", "__keyevent*:del");
            }
        }).start();
    }

    private RedisLock() {
    }

    /**
     * 以阻塞的方式锁定一个redis的key,直到拿到锁为止
     *
     * @param redisLockKey 要锁定的key
     * @param expire
     * @throws InterruptedException
     */
    public static void lock(String redisLockKey, int expire) throws InterruptedException {
        // 记录重入次数的map
        Map<String, Integer> lockReentryCountMap = LOCK_REENTRY_COUNT.get();
        if (lockReentryCountMap == null) {
            // 不存在则创建一个
            LOCK_REENTRY_COUNT.set(lockReentryCountMap = new HashMap<>());
        }

        // 取得重入次数
        int reentryCount = lockReentryCountMap.getOrDefault(redisLockKey, 0);
        if (reentryCount > 0) {
            // 不是第一次获取锁,只对次数+1
            lockReentryCountMap.put(redisLockKey, reentryCount + 1);
            return;
        }

        while (true) {
            // 先在内存中竞争
            Object newLock = new Object();
            Object lock = LOCK_MAP.putIfAbsent(redisLockKey, newLock);
            if (lock != null) {
                // 如果放入的锁不是此线程创建的,则说明有其他线程也在抢锁,让给其他线程
                synchronized (lock) {
                    // 阻塞前检测一下锁有没有被释放
                    if (LOCK_MAP.get(redisLockKey) == lock) {
                        // 等待锁释放
                        lock.wait(expire);
                    }
                }
                continue;
            }

            // 走到这,说明通过了内存竞争
            lock = newLock;
            synchronized (lock) {
                try (Jedis jedis = JEDIS_POOL.getResource()) {
                    // 然后进行redis竞争
                    if (jedis.set(redisLockKey, "", SetParams.setParams().nx().ex(expire)) != null) {
                        // redis竞争通过
                        lockReentryCountMap.put(redisLockKey, 1);
                        return;
                    }
                    // 等待锁释放
                    lock.wait(expire);
                }
            }
        }
    }

    /**
     * 解锁
     *
     * @param redisLockKey 要解锁的key
     */
    public static void unlock(String redisLockKey) {
        Map<String, Integer> lockReentryCountMap = LOCK_REENTRY_COUNT.get();
        if (lockReentryCountMap == null) {
            // 记录重入次数的map不存在,则没什么可释放的
            return;
        }

        int reentryCount = lockReentryCountMap.getOrDefault(redisLockKey, 0);
        if (reentryCount <= 0) {
            // 没有拿到过锁,也没什么可释放的
            return;
        }

        if (--reentryCount > 0) {
            // 重入次数减少后,依然大于0,则更新
            lockReentryCountMap.put(redisLockKey, reentryCount);
            return;
        }

        // 重入次数减少后为0,则释放
        try (Jedis jedis = JEDIS_POOL.getResource()) {
            // 删除计数
            lockReentryCountMap.remove(redisLockKey);
            // 释放redis锁,监听删除线程会通知抢锁线程重新竞争
            jedis.del(redisLockKey);
        }
    }

    @Override
    public void onPMessage(String pattern, String channel, String message) {
        // 获取并删除对应锁
        Object lock = LOCK_MAP.remove(message);
        if (lock == null) {
            return;
        }

        // 通知解锁
        synchronized (lock) {
            lock.notifyAll();
        }
    }
}

大家如果觉得我实现的方式有什么问题,欢迎随时指出

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值