Redis简单实践-分布式锁
日常的编码中,我们经常会遇到线程之间操作相同的资源导致并发
每一种开发技术或许都提供有代码级别的锁来避免这种并发问题,但是当服务器部署多个实例时,代码级别的锁是无法控制这样的并发的,这个时候我们遍可以通过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锁的状态监控实现类似代码中的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); }
测试结果输出如下:
通过修改代码可以实现,对锁的监听超时时间或者重试获取锁的规则,上述演示了两种锁的简单实现
setIfAbsent