Redis实现可重入分布式锁

  1. 分布式锁
    1. 通过在redis设置一个特定值,只有成功设置这个值得线程,才被看做拿到了锁,并能去使用一些资源,使用结束后删除该值,使得其他线程能去拿到高锁。由于业务逻辑中可能出现异常,导致删除操作没有被执行,引发死锁,所以一般会给锁设置一个较短的有效值。
  2. 超时问题
    1. 由于业务逻辑有可能比较复杂,导致锁已经失效,但业务逻辑还没执行结束。所以一般不建议在较长时间的业务中使用redis分布式锁。
    2. 出现超时问题时,可能会出现由于锁自动释放,前一个线程执行解锁时实际解锁了下一个线程的锁,所以建议给每个线程设施一个特定随机值,再删除锁之前检查保存的值是否和自己预期的值一致,这里类似乐观锁的思想。
  3. 可重入
    1. 通过维护当前持有锁的计数来实现可重入功能。
  4. 代码如下:
package com.xliu.chapter1;

import redis.clients.jedis.Jedis;

import java.sql.Time;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author liuxin
 * @version 1.0
 * @date 2020/4/21 14:08
 */
public class RedisWithReentrantLock {
    private ThreadLocal<Map<String, Integer>> lockers = new ThreadLocal<>();
    private ThreadLocal<String> value = new ThreadLocal<>();

    private Jedis jedis;

    public RedisWithReentrantLock(Jedis jedis) {
        this.jedis = jedis;
    }

    private boolean _lock(String key,String value) {
        return jedis.set(key, value, "nx", "ex", 60L) != null;
    }

    private void _unLock(String key) {
        jedis.del(key);
    }

    private Map<String, Integer> currentLockers() {
        Map<String, Integer> refs = lockers.get();
        if (refs != null) {
            return refs;
        }
        lockers.set(new HashMap<>());
        return lockers.get();
    }

    private boolean lock(String key) {
        Map<String, Integer> refs = currentLockers();
        Integer refCnt = refs.get(key);
        if (refCnt != null) {
            refs.put(key, refCnt + 1);
            return true;
        }
        String value = UUID.randomUUID().toString();
        boolean ok = this._lock(key,value);
        if(!ok){
            return false;
        }
        refs.put(key,1);
        this.value.set(value);
        return true;
    }

    private boolean unlock(String key){
        Map<String, Integer> refs = currentLockers();
        Integer refCnt = refs.get(key);
        if(refCnt == null){
            return false;
        }
        String s = value.get();
        if(s == null || !s.equals(jedis.get(key))){
            return false;
        }
        refCnt -= 1;
        if(refCnt > 0){
            refs.put(key,refCnt-1);
        }else{
            refs.remove(key);
            this._unLock(key);
        }
        return true;
    }

    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = new Jedis("192.168.198.128");
        RedisWithReentrantLock redis = new RedisWithReentrantLock(jedis);
        System.out.println("主线程第一次上锁:" + redis.lock("testLock"));
        System.out.println("主线程第二次上锁:" + redis.lock("testLock"));
        //停一秒保证上锁
        TimeUnit.SECONDS.sleep(1);
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " try lock " + redis.lock("testLock"));
                System.out.println(Thread.currentThread().getName() + " try unlock " +redis.unlock("testLock"));
                try {
                    TimeUnit.SECONDS.sleep(4);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " try lock " + redis.lock("testLock"));
                System.out.println(Thread.currentThread().getName() + " try unlock " +redis.unlock("testLock"));
                try {
                    TimeUnit.SECONDS.sleep(4);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " try lock " + redis.lock("testLock"));
                System.out.println(Thread.currentThread().getName() + " try unlock " +redis.unlock("testLock"));
            }
        });
        thread.start();
        //模拟业务场景,停顿3秒
        TimeUnit.SECONDS.sleep(3);
        System.out.println(redis.unlock("主线程第一次解锁:" + "testLock"));
        TimeUnit.SECONDS.sleep(3);
        System.out.println(redis.unlock("主线程第二次解锁:" + "testLock"));
    }


}

  1. 运行结果:可以看到主线程首先进行了两次加锁操作,并成功拿到了锁,然后每3秒解锁一次。线程0尝试加锁和解锁均失败,在等待两次解锁完成后才拿到了锁。
主线程第一次上锁:true
主线程第二次上锁:true
Thread-0 try lock false
Thread-0 try unlock false
主线程第一次解锁:true
Thread-0 try lock false
Thread-0 try unlock false
主线程第二次解锁:true
Thread-0 try lock true
Thread-0 try unlock true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值