redis分布式锁的实现

1. 实现原理

1.1. 使用setnx命令

  1. 加锁:setnx(lock_key,val),根据返回结果若值设置成功,则key不存在,加锁成功,反之key已经存在,加锁失败。
  2. 解锁:del(lock_key)
  3. 死锁问题:线程1获取锁成功,在未执行完任务时挂掉,没有显示的释放锁,那么其它线程就永远无法获取改锁造成死锁。所以需要设置过期时间,可以利用
    expire命令,但是setnx和expire命令是两个动作无法保证加锁操作原子性。还有个问题,假设线程1设置锁成功,但是任务没有执行完时锁已经超时,此时线程2抢占了锁,然后线程1执行完了进行del解锁,此时将会错误的对线程2进行锁释放。

1.2. 使用set(locl_key,val ,expire_time,NX)命令

针对setnx的问题,可以利用set(locl_key,val ,expire_time,NX)命令,该命令类似setnx并且可以设置过期时间,将val值设置成服务器节点地址加上当前线程id。如果业务代码执行时间大于过期时间,针对这个问题,我们可以让获得锁的线程开启一个守护线程,使用expire命令用来给快要过期的锁“续航”。比如,设置过期时间为60s,每当50s该key还存在时就进行续命50s。

2. 实现代码

2.1. 锁实现 RedisLock.java

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.net.InetAddress;
import java.util.Objects;

/**
 * @author 陈玉林
 * @desc TODO
 * @date 2020/6/28 9:42
 */
public class RedisLock {
    private String lockKey;
    private static final int EXPIRE_TIME = 30;
    private static final String SUCCESS = "OK";
    private static JedisPool jedisPool = new JedisPool();

    public RedisLock(String lockKey) {
        this.lockKey = lockKey;
    }

    public boolean lock() {
        SetParams setParams = new SetParams().nx().ex(EXPIRE_TIME);
        final String lockVal = getLockValue();
        final Jedis jedis = jedisPool.getResource();
        final String lockResult = jedis.set(lockKey, lockVal, setParams);
        jedis.close();
        boolean result = Objects.nonNull(lockResult) && SUCCESS.equals(lockResult);
        if (result) {
            GuardThread guardThread = new GuardThread(lockKey, lockVal);
            guardThread.setDaemon(true);
            guardThread.start();
        }
        return result;
    }

    /**
     * 使用主机ip+线程id作为锁的内容,在锁续命时判断
     * @return String
     */
    private String getLockValue() {
        try {
            InetAddress addr = InetAddress.getLocalHost();
            return addr.getHostAddress() + Thread.currentThread().getId();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    private class GuardThread extends Thread {
        private String key;
        private String lockVal;

        public GuardThread(String key, String lockVal) {
            super("GuardThread");
            this.key = key;
            this.lockVal = lockVal;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    //间隔1s检测一次,节约cpu资源
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                final Jedis jedis = jedisPool.getResource();
                String lockVal = jedis.get(key);
                if (this.lockVal.equals(lockVal)) {
                    final Long ttl = jedis.ttl(key);
                    System.out.println(String.format("线程【%s】持有锁剩余时间:%s", lockVal, ttl));
                    //锁寿命小于10s进行续命
                    if (ttl < 10) {
                        jedis.expire(key, EXPIRE_TIME);
                        System.out.println(String.format("线程【%s】续期后剩余时间:%s", lockVal, jedis.ttl(key)));
                    }
                }
                jedis.close();
            }
        }
    }


    public void unLock() {
        final Jedis jedis = jedisPool.getResource();
        jedis.del(lockKey);
        jedis.close();
    }

}

2.2. 测试

package com.maxch.test;

import java.util.concurrent.*;

/**
 * @author 陈玉林
 * @desc TODO
 * @date 2020/6/29 14:38
 */
public class RedisLockTest {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(3);
        service.execute(new MyTask());
        service.execute(new MyTask());
        service.execute(new MyTask());
        service.shutdown();
    }

    private static class MyTask implements Runnable {

        @Override
        public void run() {
            RedisLock redisLock = new RedisLock("1");
            boolean lock = redisLock.lock();
            if (lock) {
                System.out.println(String.format("线程【%s】获取锁成功!", Thread.currentThread().getId()));
                try {
                    //模拟业务处理时间超过锁超时时间,使用守护线程为锁续命
                    System.out.println(String.format("线程【%s】模拟阻塞40秒!", Thread.currentThread().getId()));
                    Thread.sleep(40000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    boolean unLock = redisLock.unLock();
                    if (!unLock) {
                        System.out.println(String.format("线程【%s】解锁失败!", Thread.currentThread().getId()));
                    }
                }
            } else {
                System.out.println(String.format("线程【%s】获取锁失败!", Thread.currentThread().getId()));
            }
        }
    }
}
只有把命运掌握在自己手中,从今天起开始努力,即使暂时看不到希望,也要相信自己。因为比你牛几倍的人,依然在努力。
原文地址:https://www.cnblogs.com/freesky168/p/14358169.html