Redis lock_lock锁机制原理

Redis lock_lock锁机制原理Redisson分布式锁原理1.工具类packagecom.meta.mall.common.utils;importlombok.extern.slf4j.Slf4j;importorg.redisson.api.RLock;importorg.redisson.api.RedissonClient;importorg.springframework.stereotype.Component;importjavax.annotation.Resource;import

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

Redisson 分布式锁原理
在这里插入图片描述

1. 工具类

package com.meta.mall.common.utils;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/** * redisson 分布式工具类 * * @author gaoyang * @date 2022-05-14 08:58 */
@Slf4j
@Component
public class RedissonUtils { 
   

    @Resource
    private RedissonClient redissonClient;

    /** * 加锁 * * @param lockKey */
    public void lock(String lockKey) { 
   
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
    }

    /** * 带过期时间的锁 * * @param lockKey key * @param leaseTime 上锁后自动释放锁时间 */
    public void lock(String lockKey, long leaseTime) { 
   
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
    }

    /** * 带超时时间的锁 * * @param lockKey key * @param leaseTime 上锁后自动释放锁时间 * @param unit 时间单位 */
    public void lock(String lockKey, long leaseTime, TimeUnit unit) { 
   
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, unit);
    }

    /** * 尝试获取锁 * * @param lockKey key * @return */
    public boolean tryLock(String lockKey) { 
   
        RLock lock = redissonClient.getLock(lockKey);
        return lock.tryLock();
    }

    /** * 尝试获取锁 * * @param lockKey key * @param waitTime 最多等待时间 * @param leaseTime 上锁后自动释放锁时间 * @return boolean */
    public boolean tryLock(String lockKey, long waitTime, long leaseTime) { 
   
        RLock lock = redissonClient.getLock(lockKey);
        try { 
   
            return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
        } catch (InterruptedException e) { 
   
            log.error("RedissonUtils - tryLock异常", e);
        }

        return false;
    }

    /** * 尝试获取锁 * * @param lockKey key * @param waitTime 最多等待时间 * @param leaseTime 上锁后自动释放锁时间 * @param unit 时间单位 * @return boolean */
    public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) { 
   
        RLock lock = redissonClient.getLock(lockKey);
        try { 
   
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) { 
   
            log.error("RedissonUtils - tryLock异常", e);
        }

        return false;
    }

    /** * 释放锁 * * @param lockKey key */
    public void unlock(String lockKey) { 
   
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    /** * 是否存在锁 * * @param lockKey key * @return */
    public boolean isLocked(String lockKey) { 
   
        RLock lock = redissonClient.getLock(lockKey);
        return lock.isLocked();
    }
}

2. lock和tryLock的区别

  1. 返回值
    lock 是 void;
    tryLock 是 boolean。

  2. 时机
    lock 一直等锁释放;
    tryLock 获取到锁返回true,获取不到锁并直接返回false。

lock拿不到锁会一直等待。tryLock是去尝试,拿不到就返回false,拿到返回true。
tryLock是可以被打断的,被中断的,lock是不可以。

3. 源码分析

3.1 lock

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException { 
   
		// 获取当前线程 ID
        long threadId = Thread.currentThread().getId();
		// 获取锁,正常获取锁则ttl为null,竞争锁时返回锁的过期时间
        Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) { 
   
            return;
        }

		// 订阅锁释放事件
		// 如果当前线程通过 Redis 的 channel 订阅锁的释放事件获取得知已经被释放,则会发消息通知待等待的线程进行竞争
        RFuture<RedissonLockEntry> future = subscribe(threadId);
        if (interruptibly) { 
   
            commandExecutor.syncSubscriptionInterrupted(future);
        } else { 
   
            commandExecutor.syncSubscription(future);
        }

        try { 
   
            while (true) { 
   
                // 循环重试获取锁,直至重新获取锁成功才跳出循环
                // 此种做法阻塞进程,一直处于等待锁手动释放或者超时才继续线程
                ttl = tryAcquire(-1, leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) { 
   
                    break;
                }

                // waiting for message
                if (ttl >= 0) { 
   
                    try { 
   
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) { 
   
                        if (interruptibly) { 
   
                            throw e;
                        }
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    }
                } else { 
   
                    if (interruptibly) { 
   
                        future.getNow().getLatch().acquire();
                    } else { 
   
                        future.getNow().getLatch().acquireUninterruptibly();
                    }
                }
            }
        } finally { 
   
        	// 最后释放订阅事件
            unsubscribe(future, threadId);
        }
// get(lockAsync(leaseTime, unit));
    }
    <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { 
   
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
                "if (redis.call('exists', KEYS[1]) == 0) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                        "end; " +
                        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                        "end; " +
                        "return redis.call('pttl', KEYS[1]);",
                Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
    }

此段脚本为一段lua脚本:
KEY[1]: 为你加锁的lock值
ARGV[2]: 为线程id
ARGV[1]: 为设置的过期时间

第一个if:
判断是否存在设置lock的key是否存在,不存在则利用redis的hash结构设置一个hash,值为1,并设置过期时间,后续返回锁。
第二个if:
判断是否存在设置lock的key是否存在,存在此线程的hash,则为这个锁的重入次数加1(将hash值+1),并重新设置过期时间,后续返回锁。
最后返回:
这个最后返回不是说最后结果返回,是代表以上两个if都没有进入,则代表处于竞争锁的情况,后续返回竞争锁的过期时间。

3.2 tryLock

tryLock具有返回值,true或者false,表示是否成功获取锁。tryLock前期获取锁逻辑基本与lock一致,主要是后续获取锁失败的处理逻辑与lock不一致。

    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { 
   
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
        Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) { 
   
            return true;
        }
        
        // 获取锁失败后,中途tryLock会一直判断中间操作耗时是否已经消耗锁的过期时间,如果消耗完则返回false
        time -= System.currentTimeMillis() - current;
        if (time <= 0) { 
   
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
        
        current = System.currentTimeMillis();
        // 订阅锁释放事件
        // 如果当前线程通过 Redis 的 channel 订阅锁的释放事件获取得知已经被释放,则会发消息通知待等待的线程进行竞争.
        RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
        // 将订阅阻塞,阻塞时间设置为我们调用tryLock设置的最大等待时间,超过时间则返回false
        if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) { 
   
            if (!subscribeFuture.cancel(false)) { 
   
                subscribeFuture.onComplete((res, e) -> { 
   
                    if (e == null) { 
   
                        unsubscribe(subscribeFuture, threadId);
                    }
                });
            }
            acquireFailed(waitTime, unit, threadId);
            return false;
        }

        try { 
   
            time -= System.currentTimeMillis() - current;
            if (time <= 0) { 
   
                acquireFailed(waitTime, unit, threadId);
                return false;
            }
        	
        	// 循环获取锁,但由于上面有最大等待时间限制,基本会在上面返回false
            while (true) { 
   
                long currentTime = System.currentTimeMillis();
                ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) { 
   
                    return true;
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) { 
   
                    acquireFailed(waitTime, unit, threadId);
                    return false;
                }

                // waiting for message
                currentTime = System.currentTimeMillis();
                if (ttl >= 0 && ttl < time) { 
   
                    subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else { 
   
                    subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) { 
   
                    acquireFailed(waitTime, unit, threadId);
                    return false;
                }
            }
        } finally { 
   
            unsubscribe(subscribeFuture, threadId);
        }
// return get(tryLockAsync(waitTime, leaseTime, unit));
    }

应尽量使用tryLock,且携带参数,因为可设置最大等待时间以及可及时获取加锁返回值,后续可做一些其他加锁失败的业务

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/181656.html原文链接:https://javaforall.net

(0)
上一篇 2022年10月15日 下午5:16
下一篇 2022年10月15日 下午5:16


相关推荐

  • 系统首选dns服务器修改,如何在Windows系统中设置首选DNS「建议收藏」

    系统首选dns服务器修改,如何在Windows系统中设置首选DNS「建议收藏」有时候可能需要修改或者调整DNS服务器地址的设置,以达到优化网络连接速度的效果。那么如何在Windows系统中设置首选DNS呢?具体请看下面学习啦小编介绍的操作方法!在Windows系统中设置首选DNS的方法这里Windows8为例,首先在屏幕的右下角找到“网络连接”图标,在“网络连接”图标上点击鼠标右键,然后选择“打开网络和共享中心”,接下来在“网络和共享中心”窗口中点击左侧的“更改适…

    2022年6月1日
    53
  • python连接数据库的方法,Python 连接数据库的多种方法

    python连接数据库的方法,Python 连接数据库的多种方法JZGKCHINAPyt 是一种计算机程序设计语言 它是一种动态的 面向对象的脚本语言 它是一种跨平台的 可以运行在 Windows Mac 和 Linux Unix 系统上 在日常使用中需要对大量数据进行数据分析 那么就必然用到数据库 我们常用的数据库有 SQLServer MySQL Oracle DB2 SQLite Hive PostgreSQL MongoDB 还

    2026年3月17日
    2
  • 天气太冷不想出被窝?来DIY一个离线语音控制器[通俗易懂]

    天气太冷不想出被窝?来DIY一个离线语音控制器[通俗易懂]天气太冷不想出被窝?来DIY一个离线语音控制器点击上方“Embeded小飞哥”,选择“置顶/星标公众号”干货福利,第一时间送达!成就一番伟业的唯一途径就是热爱自己的事业。如果你还没能找到让自己热爱的事业,继续寻找,不要放弃。跟随自己的心,总有一天你会找到的。——乔布斯  你去关灯,你去,你去,。。我去。。小伙伴们有没有在天气寒冷时候,想去关灯,却离不开心爱的被窝的经历呢,有的话,跟着小飞哥一起来DIY一个离线语音控制器,有了它,我们

    2022年6月23日
    26
  • 经典的进程同步问题

    经典的进程同步问题经典的进程同步问题普通版 一类进程作为生产者 生产产品 生产的产品放入一个缓冲区 消费者从缓冲区中取出产品 需要保证生产者不可以向满的缓冲区中添加产品 消费者不可以从空的缓冲区中取出产品 同一时刻只可以有一个生产者生产产品或者消费者消费产品 升级版可以实现同一个时刻既有生产者生产产品 又有消费者消费产品 但是绝对不可以同一时刻多个生产者生产产品或者多个消费者消费产品 同时使用 count 记

    2026年3月20日
    2
  • AI编程经验分享01—cursor工具使用和遇到的问题

    AI编程经验分享01—cursor工具使用和遇到的问题

    2026年3月15日
    2
  • Redis cluster集群:原理及搭建

    Redis cluster集群:原理及搭建1 为什么使用 redis redis 是一种典型的 no sql 即非关系数据库像 python 的字典一样存储 key value 键值对工作在 memory 中所以很适合用来充当整个互联网架构中各级之间的 cache 比如 lvs 的 4 层转发层 nginx 的 7 层代理层尤其是 lnmp 架构应用层如 php fpm 或者是 Tomcat 到 mysql 之间做一个 cache 以减轻 db 的压力因为有相当一部分的数据只是简单的

    2026年3月18日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号