使用redis创建自己的分布式锁

作者 QQ 1135409377 欢迎加Q 交流

RedisLock 简介

1:支付公平锁,非公平锁(只是在单机器范围内)
2:不支持锁的重入(即 同一个线程,不能获取同一把锁两次及以上)
3:一个redis的key对应一个锁
4:参数可配置
5:可以像 使用 java.util.concurrent.locks.ReentrantLock 一样使用。

RedisLock 方法介绍

不可中断,直到获取锁为止
lock(String key, int keyExpireSecond)

如果当前线程未被中断,则直到获取锁为止
lockInterruptibly(String key, int keyExpireSecond) throws InterruptedException

如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。
tryLock(String key, int keyExpireSecond)

如果锁可用,则此方法将立即返回值 true。如果锁不可用,出于线程调度目的,将挂起当前线程,如果在线程挂起期间中断线程,则排除 InterruptedException
tryLock(String key, int keyExpireSecond, long timeout, TimeUnit unit)

RedisLock 使用演示

注意:演示的demo中使用的redisClient,是我们公司自己把redis给封装了。
需要使用者把 RedisLock 的 属性 redisClient 的对象类型修改为 自己的 redis 客户端的对象类型,列入 jedis,并且 spring xml 中,<constructor-arg name="redisClient" ref="r2mClusterClient"/>
把使用者自己的 redis 客户端的对象类型,注入到 redisClient 中。

spring xml 配置
<bean id="zkProvider" class="com.wangyin.rediscluster.provider.ZkProvider">
        <constructor-arg name="appName" value="QWE"/> 
        <constructor-arg name="zkConnectionStr" value="178.28.48.208:2181,178.28.48.228:2181,178.28.48.248:2181"/>  
        <constructor-arg name="zkTimeout" value="25000"/>
    bean>

    
    <bean id="redisPoolConfig" class="com.wangyin.r2m.client.jedis.JedisPoolConfig">
        <property name="blockWhenExhausted" value="true"/> 
        <property name="maxWaitMillis" value="500" />
        <property name="maxTotal" value="15"/> 
        <property name="maxIdle" value="10"/>  
        <property name="minIdle" value="0"/> 
    bean>

    <bean id="cacheClusterConfigProvider" class="com.wangyin.rediscluster.provider.CacheClusterConfigProvider">
        <property name="providers">
            <list>
                <ref bean="zkProvider"/>
            list>
        property>
    bean>

    <bean id="r2mClusterClient" class="com.wangyin.rediscluster.client.R2mClusterClient">
        <property name="maxRedirections" value="3"/>
        <property name="redisTimeOut" value="500"/>
        <property name="redisPoolConfig" ref="redisPoolConfig"/>
        <property name="provider" ref="cacheClusterConfigProvider"/>
    bean>
    <bean id="redisLock" class="com.lp.redis.lock.RedisLock">
        
        <constructor-arg name="redisClient" ref="r2mClusterClient"/>
        
        <constructor-arg name="fair" value="true"/>
        
        <constructor-arg name="blockingAfterLockNum" value="5"/>
        
        <constructor-arg name="parkThreadMilliscond" value="50"/>
     bean>

测试代码
package com.lp.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.lp.redis.lock.RedisLock;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        final String redisKey = "keyrs";//redis key
        final int keyExpireSecond = 5;//redis key值的有效期 单位秒
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
        final RedisLock redisLock = ctx.getBean("RedisLock", RedisLock.class);
        int size = 321;
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for(int i=1; i<=size; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    redisLock.lock(redisKey, keyExpireSecond);
                    //执行业务逻辑
                    System.out.println(MySource.getAndDecrement());
                    //
                    redisLock.unlock(redisKey);
                }
            });
        }

        while(true) {
            Thread.sleep(1000);
            //打印调试信息
            System.out.println(redisLock.getContentOfKeyLockMap(redisKey));
        }
    }
}

RedisLock 代码

package com.lp.redis.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

import com.wangyin.rediscluster.client.R2mClusterClient;

public class RedisLock {

    private ReentrantLock globalLock;
    /**
     * redis的一个key,对应一个 RedisLockThread
     */
    private Map keyLockMap = new HashMap();

    private R2mClusterClient redisClient;

    /**
     * 默认值 false
     * true,公平锁; false,非公平锁
     */
    private boolean fair;

    /**
     * 尝试获取多少次锁后,如果还没有获取锁则挂起线程,默认值 2
     */
    private int blockingAfterLockNum = 2;

    /**
     * 参数只对方法 lock,lockInterruptibly 有效
     * 线程挂起时间,默认值 10毫秒
     */
    private long parkThreadMilliscond = 10;

    /**
     * 挂起线程的纳秒数
     */
    private long parkThreadNano = parkThreadMilliscond * 1000 * 1000;

    public RedisLock(R2mClusterClient redisClient) {
        this.redisClient = redisClient;
        this.globalLock = new ReentrantLock(fair);
    }

    public RedisLock(boolean fair, int blockingAfterLockNum, long parkThreadMilliscond, R2mClusterClient redisClient) {
        this.fair = fair;
        this.blockingAfterLockNum = blockingAfterLockNum;
        this.parkThreadMilliscond = parkThreadMilliscond;
        this.parkThreadNano = parkThreadMilliscond * 1000 * 1000;
        this.redisClient = redisClient;
        this.globalLock = new ReentrantLock(fair);
    }

    public boolean isFair() {
        return fair;
    }

    public R2mClusterClient getRedisClient() {
        return redisClient;
    }

    public int getBlockingAfterLockNum() {
        return blockingAfterLockNum;
    }

    public long getParkThreadMilliscond() {
        return parkThreadMilliscond;
    }

    /**
     * 不可中断,直到获取锁为止
     * @param key redis的key值
     * @param keyExpireSecond redis key的有效期
     */
    public void lock(String key, int keyExpireSecond) {
        if (isBlank(key) || keyExpireSecond < 0) {
            throw new IllegalArgumentException("param is illegal");
        }
        boolean isSaveLock = false;
        RedisLockThread lockThread = null;
        if(fair) {//公平锁
            lockThread = saveLock(key);//保证一个key对应一个lock
            lockThread.getLock().lock();//线程获取锁
            isSaveLock = true;
        }
        int doNum = 0;
        do {
            long setNx = redisClient.setnx(key, String.valueOf(System.currentTimeMillis()));
            if (setNx == 1) {
                redisClient.expire(key, keyExpireSecond);
                return;
            } else {
                if(!isSaveLock) {
                    lockThread = saveLock(key);//保证一个key对应一个lock
                    lockThread.getLock().lock();//线程获取锁
                    isSaveLock = true;
                }
                doNum ++;
                if(doNum == blockingAfterLockNum) {//获取锁几次后,让然获取不到锁,则挂起线程指定的时间。
                    doNum = 0;
                    LockSupport.parkNanos(Thread.currentThread(), parkThreadNano);
                }
            }
        } while (true);
    }

    /**
     * 如果当前线程未被中断,则直到获取锁为止
     * @param key redis的key值
     * @param keyExpireSecond redis key的有效期
     * @throws InterruptedException
     */
    public void lockInterruptibly(String key, int keyExpireSecond) throws InterruptedException  {
        if (isBlank(key) || keyExpireSecond < 0) {
            throw new IllegalArgumentException("param is illegal");
        }
        boolean isSaveLock = false;
        RedisLockThread lockThread = null;
        if(fair) {//公平锁
            lockThread = saveLockInterruptibly(key);//保证一个key对应一个lock
            lockThread.getLock().lockInterruptibly();//线程获取锁
            isSaveLock = true;
        }
        int doNum = 0;
        do {
            long setNx = redisClient.setnx(key, String.valueOf(System.currentTimeMillis()));
            if (setNx == 1) {
                redisClient.expire(key, keyExpireSecond);
                return;
            } else {
                if(!isSaveLock) {
                    lockThread = saveLockInterruptibly(key);//保证一个key对应一个lock
                    lockThread.getLock().lockInterruptibly();//线程获取锁
                    isSaveLock = true;
                }
                doNum ++;
                if(doNum == blockingAfterLockNum) {//获取锁几次后,让然获取不到锁,则挂起线程指定的时间。
                    doNum = 0;
                    LockSupport.parkNanos(Thread.currentThread(), parkThreadNano);
                    if(Thread.interrupted()) {
                        throw new InterruptedException();
                    }
                }
            }
        } while (true);
    }

    /**
     * 如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。
     * @param key redis的key值
     * @param keyExpireSecond redis key的有效期
     * @return 如果获取了锁,则返回 true 否则返回 false。 
     */
    public boolean tryLock(String key, int keyExpireSecond) {
        if (isBlank(key) || keyExpireSecond < 0) {
            throw new IllegalArgumentException("param is illegal");
        }
        long setNx = redisClient.setnx(key, String.valueOf(System.currentTimeMillis()));
        if (setNx == 1) {
            redisClient.expire(key, keyExpireSecond);
            return true;
        }
        return false;
    }

    /**
     * 如果锁可用,则此方法将立即返回值 true。
     * 如果锁不可用,出于线程调度目的,将挂起当前线程,如果在线程挂起期间中断线程,则排除 InterruptedException
     * @param key redis的key值
     * @param keyExpireSecond redis key的有效期
     * @param timeout
     * @param unit
     * @return
     * @throws InterruptedException
     */
    public boolean tryLock(String key, int keyExpireSecond, long timeout, TimeUnit unit) throws InterruptedException {
        long lastTime = System.nanoTime();
        long nanosTimeout = unit.toNanos(timeout);
        if (isBlank(key) || keyExpireSecond < 0) {
            throw new IllegalArgumentException("param is illegal");
        }
        //
        boolean isSaveLock = false;
        RedisLockThread lockThread = null;
        if(fair) {//公平锁
            lockThread = saveLockInterruptibly(key);
            boolean isLock = lockThread.getLock().tryLock(timeout, unit);
            if(!isLock) {//没有获取锁
                lockThread.removeThreadOfLock(Thread.currentThread());//删除lock对应的线程
                return isLock;
            } else {
                isSaveLock = true;
            }
        }
        //
        int doNum = 0;//do{}while 循环多少次挂起线程
        do {
            long setNx = redisClient.setnx(key, String.valueOf(System.currentTimeMillis()));
            if (setNx == 1) {
                redisClient.expire(key, keyExpireSecond);
                return true;
            } else {
                if(!isSaveLock) {//非公平锁
                    lockThread = saveLockInterruptibly(key);
                    boolean isLock = lockThread.getLock().tryLock(timeout, unit);
                    if(!isLock) {
                        lockThread.removeThreadOfLock(Thread.currentThread());//删除lock对应的线程
                        return isLock;
                    } else {
                        isSaveLock = true;
                    }
                }
                //超时
                if (nanosTimeout <= 0) {
                    lockThread.removeThreadOfLock(Thread.currentThread());//删除lock对应的线程
                    lockThread.getLock().unlock();//lock释放锁
                    //删除 key 对应的锁
                    globalLock.lock();
                    if(lockThread.getThreadsOfLock() == 0) {//lock对应的线程数为 0,从集合中删除 key对应的lock。
                        keyLockMap.remove(key);
                    }
                    globalLock.unlock();
                    return false;
                }
                //循环获取锁
                doNum ++;
                if(doNum == blockingAfterLockNum) {
                    doNum = 0;
                    long now = System.nanoTime();
                    nanosTimeout -= now - lastTime;
                    LockSupport.parkNanos(Thread.currentThread(), nanosTimeout);
                    if (Thread.interrupted()) {
                        throw new InterruptedException();
                    }
                }
            }
        } while (true);
    }

    /**
     * 释放锁
     * @param key redis的key值
     */
    public void unlock(String key) {
        globalLock.lock();
        redisClient.del(key);//必须先删除redis的key
        RedisLockThread lockThread = keyLockMap.get(key);//获取key对应的lock
        if(lockThread != null) {
            if ( Thread.currentThread() != lockThread.getThreadOfLock(Thread.currentThread()) ) {//当前线程是否在,lock对应的线程集合内
                throw new IllegalMonitorStateException();
            } else {
                lockThread.removeThreadOfLock(Thread.currentThread());//删除lock对应的线程
                lockThread.getLock().unlock();//lock释放锁
                if(lockThread.getThreadsOfLock() == 0) {//lock对应的线程数为 0,从集合中删除 key对应的lock。
                    keyLockMap.remove(key);
                }
            }
        }
        globalLock.unlock();
    }

    /**
     * 保证一个key对应一个lock
     * key与lock是一对一的关系,lock与线程是一对多的关系
     * @param key
     * @return
     * @throws InterruptedException 
     */
    private RedisLockThread saveLock(String key) {
        globalLock.lock();//保证 saveLock 方法是原子操作
        //保证一个 key 只创建一个 lock
        RedisLockThread lockThread = keyLockMap.get(key);
        if(lockThread == null) {
            lockThread = new RedisLockThread(fair);
            lockThread.saveThreadOfLock(Thread.currentThread());//保存 lock 与 线程的关系
            keyLockMap.put(key, lockThread);//保存 key 与  lock 的关系
        }
        lockThread.saveThreadOfLock(Thread.currentThread());//保存 lock 与 线程的关系
        globalLock.unlock();
        return lockThread;
    }


    /**
     * 保证一个key对应一个lock
     * key与lock是一对一的关系,lock与线程是一对多的关系
     * @param key
     * @return
     * @throws InterruptedException 
     */
    private RedisLockThread saveLockInterruptibly(String key) throws InterruptedException{
        globalLock.lockInterruptibly();//保证 saveLockInterruptibly 方法是原子操作
        //保证一个 key 只创建一个 lock
        RedisLockThread lockThread = keyLockMap.get(key);
        if(lockThread == null) {
            lockThread = new RedisLockThread(fair);
            lockThread.saveThreadOfLock(Thread.currentThread());//保存 lock 与 线程的关系
            keyLockMap.put(key, lockThread);//保存 key 与  lock 的关系
        }
        lockThread.saveThreadOfLock(Thread.currentThread());//保存 lock 与 线程的关系
        globalLock.unlock();
        return lockThread;
    }

    private boolean isBlank(CharSequence cs) {
        int strLen;
        if (cs == null || (strLen = cs.length()) == 0)
            return true;
        for (int i = 0; i < strLen; i++)
            if (!Character.isWhitespace(cs.charAt(i)))
                return false;

        return true;
    }

    /**
     * 调试使用
     * @return
     */
    public String getContentOfKeyLockMap(String key){
        StringBuilder str = new StringBuilder();
        str.append("keyLock count:").append(keyLockMap.size());

        RedisLockThread lockThread = keyLockMap.get(key);
        if(lockThread != null) {
            str.append(",keyLockInfo:").append(lockThread.toString());
        } else {
            str.append(",keyLockInfo:").append("no lock");
        }

        return str.toString();
    }

    /**
     * redis的一个key,对应一个 RedisLockThread 实例对象
     */
    class RedisLockThread {

        private ReentrantLock lock;

        private volatile Map  exclusiveOwnerThread = new HashMap();

        public RedisLockThread(boolean fair) {
            this.lock = new ReentrantLock(fair);
        }

        protected ReentrantLock getLock() {
            return lock;
        }

        protected void saveThreadOfLock(Thread thread) {
            exclusiveOwnerThread.put(thread, thread);
        }

        protected Thread getThreadOfLock(Thread thread) {
            return exclusiveOwnerThread.get(thread);
        }

        protected void removeThreadOfLock(Thread thread) {
            exclusiveOwnerThread.remove(thread);
        }

        protected int getThreadsOfLock() {
            return exclusiveOwnerThread.size();
        }

        @Override
        public String toString() {
            StringBuffer str = new StringBuffer();
            str.append(" thread count of keyLock=").append(exclusiveOwnerThread.size());
            str.append(",keyLock=").append(lock.toString()).append(" ");
            return str.toString();
        }
    }
}

问与答

问:使用redis的什么指令实现的,分布式锁。
答:setnx

问:集群中有两台机器A、B,每台机器分别有10个线程需要获取redis lock。如果B机器的某个线程获取了redis lock,并且一直没有释放。这时 A 机器的10个线程会都挂起吗?
答:如果A机器的10个线程都挂起的话,那么将无法唤醒10个线程。所以 A 机器的10线程其中有9个会挂起,另外的一个线程会不断的尝试获取 redis lock,当获取blockingAfterLockNum参数配置次数后,仍然没有获取redis lock的时候,则挂起 parkThreadMilliscond 参数配置的时间。挂起结束后继续尝试获取 redis lock。

你可能感兴趣的:(redis,分布式)