【工具类】基于Redis的分布式可重入锁(Java)

这篇博客介绍了如何基于Redis实现一个可重入锁,采用Spring注入方式,包括Lock接口设计、非阻塞的自旋+CAS实现,以及探讨使用BLPOP进行阻塞等待的可能性。文章提到该实现未经过大规模测试,欢迎读者提出使用中遇到的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于Redis的可重入锁,这个版本通过spring注入方式完成的Service版本,有时间写一个Util的版本。

1.定义的Lock接口,与JDK内置的类似,添加了过期时间,没有支持Condition。


import java.util.concurrent.TimeUnit;

/**
 * @author : wudi28
 * @date : 2018/9/24 18:35
 */
public interface IDistributedLockService {

    /**
     * 尝试获取锁(自旋)
     * @param key 锁对应的key
     * @param timeout 等待锁的超时时间
     * @param unit 等待锁的超时时间单位
     * @param expireSecs 锁的过期时间(秒)
     * */
    boolean tryLock(String key, long timeout, TimeUnit unit, long expireSecs);

    /**
     * 尝试获取锁,仅一次,获取不到直接返回
     * @param key 锁对应的key
     * @param expireSecs 锁的过期时间(秒)
     * */
    boolean tryLock(String key, long expireSecs);


    /**
     * 阻塞获取锁
     * @param key 锁对应的key
     * @param expireSecs 锁的过期时间(秒)
     * */
    void lock(String key, long expireSecs);

    /**
     * 释放锁
     * @param key 锁对应的key
     * */
    void unlock(String key);

}

2.实现类,需要使用方替换RedisService,实现其中的set,get,del方法,一般大家都会有专门的Redis工具类或者原生

2.1同步实现的方式没有使用阻塞的方式,使用简单的自旋+CAS,缺点是占CPU, 再有就是非公平的(不是FIFO)

2.2可以换成Redis中的BLPOP来实现阻塞waiting,当持有锁线程释放锁时,RPUSH一条数据,相当于notify。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author : wudi28
 * @date : 2018/9/24 18:51
 */


@Service
public class RedisReentrantDistributedLockService implements IDistributedLockService{

    @Resource
    private RedisService redisService;

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisReentrantDistributedLockService.class);

    /**分布式线程的唯一标识,使用本机IP+UUID形式,释放锁后会删除,防止线程池方式的重入锁*/
    private static ThreadLocal<String> TOKEN_MAP = new ThreadLocal<>();

    /**本机IP*/
    private static String IP;

    /**Redis成功响应码*/
    private static final String OK = "OK";

    /**Redis中的key前缀*/
    private static final String LOCK_PREFIX = "LOCK_PREFIX_";

    /**最大等待秒数*/
    private static final long FOREVER = Long.MAX_VALUE;

    private static final String METHOD_DESC = "Redis分布式锁";

    static {
        try {
            IP = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            LOGGER.error("分布式Redis锁,获取IP异常", e);
            IP = "UNKNOWN_HOST";
        }
    }


    @Override
    public boolean tryLock(String key, long time, TimeUnit unit, long expireSecs) {
        if (tryLock(key, expireSecs)) {
            LOGGER.debug(METHOD_DESC +",直接tryLock成功|key={}|token={}", key, getToken());
            return true;
        }
        boolean held = false;
        long start = System.nanoTime();

        //自旋CAS,获取不到锁 && 没有超时
        while (!held && (System.nanoTime() - start) < unit.toNanos(time)) {
            held = getLock(key, expireSecs);
        }
        if (!held) {
            LOGGER.debug(METHOD_DESC +",tryLock超时|key={}|token={}", key, getToken());
        } else {
            LOGGER.debug(METHOD_DESC +",自旋tryLock成功|key={}|token={}", key, getToken());
        }
        return held;
    }

    @Override
    public boolean tryLock(String key, long expireSecs) {

        //尝试获取锁
        if (getLock(key, expireSecs)) {
            return true;
        }

        //如果设置token失败,查询缓存中的token是否与自己的相同,如果相同返回true,即重入
        return getToken().equals(getLockToken(key));
    }

    @Override
    public void lock(String key, long expireSecs) {
        tryLock(key, FOREVER, TimeUnit.SECONDS, expireSecs);
    }

    @Override
    public void unlock(String key) {
        releaseLock(key);
        TOKEN_MAP.remove();
        LOGGER.debug(METHOD_DESC +",释放锁成功|key={}|token={}", key, getToken());
    }

    private String getToken(){
        String threadToken = TOKEN_MAP.get();
        if (null == threadToken || threadToken.length() < 1) {
            TOKEN_MAP.set(threadToken = IP + "_" + UUID.randomUUID().toString().replaceAll("-", ""));
        }
        return threadToken;
    }


    /**
     * 使用redis的原子方法set,设置当前线程的token,设置成功即获取到锁
     *
     * */
    private boolean getLock(String key, long expireSecs) {
        //NX表示只有对应的key不存在时,才设置, XX表示只有key存在时,才设置
        //EX表示设置的超时时间单位为秒 PX为毫秒
        return OK.equals(redisService.set(keyOf(key), getToken(), "NX", "EX", expireSecs));
    }

    /**
     * 获取当前持有锁的token
     * */
    private String getLockToken(String key) {
        return redisService.get(keyOf(key));
    }

    /**
     * 释放锁,即删除对应的key
     * */
    private void releaseLock(String key) {
        redisService.del(keyOf(key));
    }

    /**
     * key+前缀
     * */
    private String keyOf(String key) {
        return LOCK_PREFIX + key;
    }
}

3.没有经过大量测试,有使用问题,下方留言

public class Test{

    @Resource
    private IDistributedLockService iDistributedLockService;

    private static ExecutorService queryThreadPool = Executors.newCachedThreadPool();

    @Test
    public void testLock() throws ExecutionException, InterruptedException {
        List<Future> futures = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            futures.add(queryThreadPool.submit(new LockThread(iDistributedLockService)));
        }

        for (Future future : futures) {
            future.get();
        }

  /*
       输出: 
       pool-1-thread-1,开始
        pool-1-thread-3,开始
        pool-1-thread-2,开始
        pool-1-thread-5,开始
        pool-1-thread-4,开始
        pool-1-thread-3,获取锁成功
        pool-1-thread-3,重入锁成功
        pool-1-thread-3,释放锁成功
        pool-1-thread-1,获取锁成功
        pool-1-thread-1,重入锁成功
        pool-1-thread-1,释放锁成功
        pool-1-thread-2,获取锁成功
        pool-1-thread-2,重入锁成功
        pool-1-thread-2,释放锁成功
        pool-1-thread-5,获取锁成功
        pool-1-thread-5,重入锁成功
        pool-1-thread-5,释放锁成功
        pool-1-thread-4,获取锁成功
        pool-1-thread-4,重入锁成功
        pool-1-thread-4,释放锁成功*/

    }

    public static class LockThread implements Runnable {

        private IDistributedLockService lockService;

        public LockThread(IDistributedLockService lockService) {
            this.lockService = lockService;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ",开始");
            lockService.lock("TEST_KEY",  10);
            System.out.println(Thread.currentThread().getName() + ",获取锁成功");
            lockService.lock("TEST_KEY",  10);
            System.out.println(Thread.currentThread().getName() + ",重入锁成功");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lockService.unlock("TEST_KEY");
            System.out.println(Thread.currentThread().getName() + ",释放锁成功");
        }
    }
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值