Redis实现分布式锁

本文详细介绍了如何使用Redis实现分布式锁,包括基于setnx命令和set命令带过期时间的实现方式,以及如何处理可能出现的死锁问题。通过设置锁的过期时间和守护线程进行续命来确保锁的正确释放。此外,文中还提供了Java代码示例进行演示。

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

Redis系列

1. 基于setnx命令实现

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

2. set(locl_key,val ,expire_time,NX)

针对setnx的问题,可以利用set(locl_key,val ,expire_time,NX)命令,该命令类似setnx并且可以设置过期时间,同时让获得锁的线程开启一个守护线程,使用expire命令用来给快要过期的锁“续航”。比如,设置过期时间为60s,每当50s该key还存在时就进行续命50s。
实现代码

import redis.clients.jedis.*;
import redis.clients.jedis.params.SetParams;

import java.net.InetAddress;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

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

    private final ShardedJedisPool jedisPool;

    {
        // 配置Redis信息
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(100);
        config.setMaxIdle(50);
        config.setMaxWaitMillis(3000);
        config.setTestOnBorrow(true);
        config.setTestOnReturn(true);

        JedisShardInfo jedisShardInfo1 = new JedisShardInfo("localhost");

        jedisShardInfo1.setPassword("123456");

        List<JedisShardInfo> list = new LinkedList<>();
        list.add(jedisShardInfo1);
        jedisPool = new ShardedJedisPool(config, list);
    }

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

    public boolean lock() {
        SetParams setParams = new SetParams().nx().ex(EXPIRE_TIME);
        final String lockVal = getLockValue();
        final ShardedJedis 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 final String key;
        private final 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资源
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                final ShardedJedis jedis = jedisPool.getResource();
                String lockVal = jedis.get(key);
                if (this.lockVal.equals(lockVal)) {
                    final Long ttl = jedis.ttl(key);
                    System.out.printf("节点【%s】持有锁剩余时间:%s%n", lockVal, ttl);
                    //锁寿命小于10s进行续命
                    if (ttl < 10) {
                        jedis.expire(key, EXPIRE_TIME);
                        System.out.printf("节点【%s】续期后剩余时间:%s%n", lockVal, jedis.ttl(key));
                    }
                }
                jedis.close();
            }
        }
    }


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

}

测试代码

import java.util.concurrent.TimeUnit;

public class Test {

    public static void main(String[] args) {
        RedisLock lock = new RedisLock("myLock");

        new Thread(new MyTask(lock), "张三").start();
        new Thread(new MyTask(lock), "李四").start();
    }

    public static void readStore() {
        RedisLock lock = new RedisLock("myLock");

        new Thread(new MyTask(lock), "张三").start();
        new Thread(new MyTask(lock), "李四").start();
    }

    private static class MyTask implements Runnable {
        final RedisLock lock;
        public MyTask(RedisLock lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            boolean locked = this.lock.lock();
            if (locked) {
                //模拟读取数据库
                System.out.println(Thread.currentThread().getName() + ",正在读取中...");
                try {
                    TimeUnit.SECONDS.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.lock.unLock();
                System.out.println(Thread.currentThread().getName() + ",完成数据读取,释放锁");
            }else{
//                System.out.println(Thread.currentThread().getName() + ",需要排队");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.run();
            }

        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值