Redis简单实践-分布式锁

loviezhang 2020-04-23

日常的编码中,我们经常会遇到线程之间操作相同的资源导致并发

每一种开发技术或许都提供有代码级别的锁来避免这种并发问题,但是当服务器部署多个实例时,代码级别的锁是无法控制这样的并发的,这个时候我们遍可以通过Redis来控制功能对锁的获取,由于应用程序和Redis之间是通过网络来进行交流,无论是单机还是集群,无论是单线程还是多线程,利用Redis来实现锁都能够使用。

用Redis来创建锁,只要目标则是实现“占坑”,当前上下文我们将其称之为lock,假设一个线程已经占用了这个lock,那么其他的线程则无法再去占用这个lock,假设我们将占用的操作设置为set lock 1,那么则需要让其他的线程无法去set lock 1,常规的流程就是首先,获取到lock,如果lock没有被设置值,则设置进去,占用这个lock,但是同样的,这里在多线程和多实例的情况下会有并发问题,因为【获取到lock,如果lock没有被设置值,则设置进去,占用这个lock】这个操作并不是原子操作,可能在执行过程中,lock就被其他线程给占用设置,这种情况下,会由不止一个线程能够获取到锁。

为了解决这样的并发问题,在Redis中,提供了setnx指令来将【读取判断设置】的操作原子化,又由于Redis是一个单线程的执行模式,setnx将会只有一个线程能够设置值,通过这样的方法来实现锁的占用。而释放锁的方式则可以通过直接删除这个lcok的key或者设置过期时间,这里不是最主要的讨论目的

下面列举一个最简单的锁,控制几个线程中,只能由一个线程能够执行到逻辑代码,代码由JAVA编写,访问Redis利用RedisTemplate,在Redistemplate中setnx对应的方法为setIfAbsent代码如下:

首先建立一个RedisLock的类,编写【读取判断设置】的操作以便于判断是否拿到锁:

@Component
public class RedisLock {

    @Autowired
    StringRedisTemplate redisTemplate;

    public boolean isLock(String lockKey) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent("lock:" + lockKey, "1",
                Duration.ofSeconds(5));
        if (result) {
            return false;
        } else {
            return true;
        }
    }
}

建立一个MyThread来模拟多线程多实例并发占用某个Redis锁:

public class MyThread extends Thread {
        int i;
        private RedisLock redisLock;

        public MyThread(int i, RedisLock redisLock) {
            super();
            this.i = i;
            this.redisLock = redisLock;
        }

        @Override
        public void run() {
            final double d = Math.random();
            final int sleep = (int)(d*100);
            try {          //随机睡眠
                Thread.sleep(sleep);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (redisLock.isLock("test")) {
                System.out.println("thread" + i + ": locked");
            } else {
                System.out.println("thread" + i + ": no lock");
            }
        }
    }

运行十个线程测试锁的获取情况

@Test
    void contextLoads() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread thread = new MyThread(i, redisLock);
            thread.start();
        }

        Thread.sleep(1000);
    }

结果截图如下:

Redis简单实践-分布式锁

上述是一个加锁的简单实现,只有拿到锁的代码可以执行业务逻辑,同时也可以通过对Redis锁的状态监控实现类似代码中的lock等待

修改RedisLock代码如下:

@Component
public class RedisLock {

    @Autowired
    StringRedisTemplate redisTemplate;

    public boolean isLock(String lockKey) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent("lock:" + lockKey, "1",
                Duration.ofSeconds(5));
        if (result) {
            return false;
        } else {
            return true;
        }
    }

    public void unLock(String lockKey) {
        redisTemplate.delete("lock:" + lockKey);
    }
}

提供unlock方法以供释放锁

增加测试类Thread_Wait

public class MyThread_Wait extends Thread {
        int i;
        private RedisLock redisLock;

        public MyThread_Wait(int i, RedisLock redisLock) {
            super();
            this.i = i;
            this.redisLock = redisLock;
        }

        @Override
        public void run() {
            try {
                final double d = Math.random();
                final int sleep = (int) (d * 100);
                try {
                    Thread.sleep(sleep);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                int loopCount = 0;
                String key = "test";
                while (redisLock.isLock(key)) {
                    loopCount++;
                    Thread.sleep(10);
                }


                Thread.sleep(50);
                System.out.println("thread" + i + " wait:" + loopCount * 10);

                redisLock.unLock(key);
            } catch (InterruptedException e) {

            }
        }
    }

开启十个线程等待锁的释放:

@Test
    void contextLoads() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread thread = new MyThread_Wait(i, redisLock);
            thread.start();
        }

        Thread.sleep(3000);
    }

测试结果输出如下:

Redis简单实践-分布式锁

通过修改代码可以实现,对锁的监听超时时间或者重试获取锁的规则,上述演示了两种锁的简单实现

setIfAbsent

相关推荐

DiamondTao / 0评论 2020-08-30