手写redis分布式锁(一)
实现分布式锁时需要实现的两个基本方法:
1、获取锁//添加锁,利用setnx的互斥性 setnx lock thread1
2、释放锁//释放锁,删除即可 del lock
以上获取锁和释放锁有一个bug,就是当一个线程获取到锁的时候,这个时候redis服务器突然宕机了,还没有来得及释放锁,那么lock这把锁就会一直存在内存中了。所有线程都不能再获取到这把锁;
所以我们要对lock加一个超时时间//带超时时间的锁 set lock thread nx ex 10
以下是具体代码实现:/** * 1、线程如果获取不到锁,直接返回false,不会等待 */ @SpringBootTest class RedisTestApplicationTests { @Autowired private StringRedisTemplate stringRedisTemplate; //锁的前缀 private static final String PREFIX_LOCK = "lock:"; //锁的名字(这里应该通过参数传递进来,测试我们可以固定一个值就行) private String lockName = "orderId"; @Test void lock() { //获取当前线程的标识id,将id作为值 long id = Thread.currentThread().getId(); Boolean success = stringRedisTemplate.opsForValue().setIfAbsent( PREFIX_LOCK + lockName, id + "", 20, TimeUnit.SECONDS); System.out.println(success); } @Test void unlock() { stringRedisTemplate.delete(PREFIX_LOCK + lockName); } }
以上是最简单的一个分布式锁,但是还有很多缺陷,我们先说第一个缺陷;
1、会存在误删导致线程并发的问题,以下是具体分析过程1、3个线程同时竞争一把锁,线程1先获取到锁,并设置锁的有效期为10秒钟;
2、线程1由于网络原因,10秒钟还没有执行完代码,这个时候10秒钟时间已经到了,redis自动将这把锁给删除掉;
3、锁被删除了,线程2和线程3立马竞争这把锁,假设线程2获取到这把锁,这个时候就有两个线程同时执行了(线程1和线程2),这个时候线程1执行完了,又会去删除这把锁;
4、那么线程3就会获取锁,又进入到方法里面,这个时候线程2和线程3又发生了并发;
针对以上情况,我们发现在删除锁的时候,一定要判断一下当前的锁,是不是当前线程加的锁,所以我们对释放锁的代码进行修改 @SpringBootTest class RedisTestApplicationTests { @Autowired private StringRedisTemplate stringRedisTemplate; //锁的前缀 private static final String PREFIX_LOCK = "lock:"; //锁的前缀(这里应该通过参数传递进来,测试我们可以固定一个值就行) private String lockName = "orderId"; //为了防止多个JVM线程id重复问题,这里加入UUID作为区分 private static final String ID_PREFIX = UUID.randomUUID().toString() + "-"; @Test void contextLoads() { //获取当前线程的标识id long id = Thread.currentThread().getId(); Boolean success = stringRedisTemplate.opsForValue().setIfAbsent( PREFIX_LOCK + lockName, ID_PREFIX + id, 20, TimeUnit.SECONDS); System.out.println(success); } @Test void unlock() { //获取当前线程标识和UUID String threadid = ID_PREFIX + Thread.currentThread().getId(); //获取redis里面锁的值 String redisId = stringRedisTemplate.opsForValue().get(PREFIX_LOCK + lockName); if (threadid.equals(redisId)){ stringRedisTemplate.delete(PREFIX_LOCK + this.lockName); } } }