Redis——实现悲观锁

Posted 水田如雅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis——实现悲观锁相关的知识,希望对你有一定的参考价值。

抽象类:

/**
 * \\* Created: liuhuichao
 * \\* Date: 2018/6/5
 * \\* Time: 上午10:35
 * \\* Description: redis 锁抽象类
 * \\
 */
public abstract class AbstractLock implements Lock

    protected volatile boolean locked;

    private Thread exclusiveOwnerThread;

    @Override
    public void lock() 
        try 
            lock(false, 0, null, false);
         catch (InterruptedException e) 

        
    

    @Override
    public void lockInterruptibly() throws InterruptedException 
        lock(false, 0, null, true);
    

    public boolean tryLockInterruptibly(long time, TimeUnit unit) throws InterruptedException 
        return lock(true, time, unit, true);
    

    @Override
    public boolean tryLock(long time, TimeUnit unit)  
        try 
            System.out.println("try--lock");
            return lock(true, time, unit, false);
         catch (InterruptedException e) 
            e.printStackTrace();
            System.out.println("" + e);
        
        return false;
    

    @Override
    public void unlock() 
        // 检查当前线程是否持有锁
        if (Thread.currentThread() != getExclusiveOwnerThread()) 
            throw new IllegalMonitorStateException("current thread does not hold the lock");
        
        unlock0();
        setExclusiveOwnerThread(null);
    

    protected void setExclusiveOwnerThread(Thread thread) 
        exclusiveOwnerThread = thread;
    

    protected final Thread getExclusiveOwnerThread() 
        return exclusiveOwnerThread;
    

    protected abstract void unlock0();

    /**
     * 阻塞式获取锁的实现
     * @param useTimeout
     * @param time
     * @param unit
     * @param interrupt 是否响应中断
     * @return
     * @throws InterruptedException
     */
    protected abstract boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt)
            throws InterruptedException;

实现类:

/**
 * \\* Created: liuhuichao
 * \\* Date: 2018/6/5
 * \\* Time: 下午4:09
 * \\* Description:基于Redis的SETNX操作实现的分布式锁基于Redis的SETNX操作实现的分布式锁
 *          获取锁时最好用lock(long time, TimeUnit unit), 以免网路问题而导致线程一直阻塞
 *
 *       SETNX命令(SET if Not eXists)
             语法:
             SETNX key value
             功能:
             当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

         GETSET命令(这是一个原子命令!)
             语法:
             GETSET key value
             功能:
             将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。
 * \\
 */
public class RedisBasedOnSetNxLock extends AbstractLock


    private Jedis jedis;

    // 锁的名字
    protected String lockKey;

    // 锁的有效时长(毫秒)
    protected long lockExpires;

    public RedisBasedOnSetNxLock(Jedis jedis, String lockKey, long lockExpires) 
        this.jedis = jedis;
        this.lockKey = lockKey;
        this.lockExpires = lockExpires;
    

    @Override
    protected void unlock0() 

    

    @Override
    protected boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) throws InterruptedException 
        if(interrupt)
            checkInterruption();
        
        long start = System.currentTimeMillis();
        long timeout = unit.toMillis(time);
        while (useTimeout ? isTimeout(start, timeout) : true) 
            if (interrupt) 
                checkInterruption();
            
            long lockExpireTime = System.currentTimeMillis() + lockExpires + 1;// 锁超时时间
            String stringOfLockExpireTime = String.valueOf(lockExpireTime);
            if (jedis.setnx(lockKey, stringOfLockExpireTime) == 1)  // 获取到锁
                //成功获取到锁, 设置相关标识
                locked = true;
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            
            String value = jedis.get(lockKey);
            if (value != null && isTimeExpired(value))  // lock is expired
                // 假设多个线程(非单jvm)同时走到这里
                String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime); //原子操作
                /**
                 * 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)
                 * 假如拿到的oldValue依然是expired的,那么就说明拿到锁了
                 */
                if (oldValue != null && isTimeExpired(oldValue)) 
                    //成功获取到锁, 设置相关标识
                    locked = true;
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                
             else 
                // TODO lock is not expired, enter next loop retrying
            
        
        return false;
    

    public boolean tryLock() 
        long lockExpireTime = System.currentTimeMillis() + lockExpires + 1;// 锁超时时间
        String stringOfLockExpireTime = String.valueOf(lockExpireTime);

        if (jedis.setnx(lockKey, stringOfLockExpireTime) == 1)  // 获取到锁
            // 成功获取到锁, 设置相关标识
            locked = true;
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        

        String value = jedis.get(lockKey);
        if (value != null && isTimeExpired(value))  // lock is expired
            // 假设多个线程(非单jvm)同时走到这里
            String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime); //原子操作
            // 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)
            // 假如拿到的oldValue依然是expired的,那么就说明拿到锁了
            if (oldValue != null && isTimeExpired(oldValue)) 
                //成功获取到锁, 设置相关标识
                locked = true;
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            
         else 
            // TODO lock is not expired, enter next loop retrying
        
        return false;
    



    private void checkInterruption() throws InterruptedException 
        if (Thread.currentThread().isInterrupted()) 
            throw new InterruptedException();
        
    

    private boolean isTimeExpired(String value) 
        return Long.parseLong(value) < System.currentTimeMillis();
    

    /**
     * judge whether timeout or not
     * @param start
     * @param timeout
     * @return if timeout return true; else false
     */
    private boolean isTimeout(long start, long timeout) 
        return start + timeout > System.currentTimeMillis();
    

    private void doUnlock() 
        jedis.del(lockKey);
    

    @Override
    public Condition newCondition() 
        return null;
    

锁续命

加锁之后,开启一个线程,对当前加锁的操作,进行不断的更新过期时间的操作,直到锁被释放;
我们redis的客户端redisson提供了锁续命以及加锁解锁的安全操作。

 RLock lock = client.getNativeClient().getLock("key");
        try
             lock.lock();
            //这里写加锁的逻辑。。。
            
        finally 
            lock.unlock();
        

以上是关于Redis——实现悲观锁的主要内容,如果未能解决你的问题,请参考以下文章

PHP+Redis 实现悲观锁机制

php redis 常见应用 并发锁。(悲观锁)

redis中的乐观锁和悲观锁

redis--悲观锁乐观锁

用redis实现悲观锁(后端语言以php为例)

Redis学习Redis分布式锁实现秒杀业务(乐观锁悲观锁)