1 分布式锁 在之前分析过,集群模式下的一人一单存在问题,其根源就在锁监视器上。因此需要一个分布式锁,满足分布式系统 或集群 模式下多进程可见并且互斥 的锁。
核心思想:让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行。
1.1 分布式要求
可见性:多个线程都能看到相同的结果,注意:这个地方说的可见性并不是并发编程中指的内存可见性,只是说多个进程之间都能感知到变化的意思
互斥:互斥是分布式锁的最基本的条件,使得程序串行执行
高可用:程序不易崩溃,时时刻刻都保证较高的可用性
高性能:由于加锁本身就让性能降低,所有对于分布式锁本身需要他就较高的加锁性能和释放锁性能
安全性:安全也是程序中必不可少的一环
1.2 常见的分布式锁
1.3 redis实现分布式锁
获取锁:要求确保当前只能有一个线程获取到锁。(set key value ex ttl nx )利用set nx的特性保证只能有一个获取线程
释放锁:要求可以手动释放,也要有超时释放做兜底。(del key)
1.4 加锁 增加一个接口:ILock.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.hmdp.lock;public interface ILock { boolean tryLock (long expireTime) ; void unLock () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class SimpleRedisLock implements ILock { private static final String KEY_PREFIX = "lock:" ; private String lockName; StringRedisTemplate stringRedisTemplate; public SimpleRedisLock (String lockName, StringRedisTemplate stringRedisTemplate) { this .lockName = lockName; this .stringRedisTemplate = stringRedisTemplate; } @Override public boolean tryLock (long expireTime) { long threadId = Thread.currentThread().getId(); Boolean flag = stringRedisTemplate.opsForValue() .setIfAbsent(KEY_PREFIX + lockName, threadId + "" , expireTime, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } @Override public void unLock () { stringRedisTemplate.delete(KEY_PREFIX + lockName); } }
修改VoucherOrderServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 @Service public class VoucherOrderServiceImpl extends ServiceImpl <VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Autowired private ISeckillVoucherService iSeckillVoucherService; @Autowired private RedisWorker redisWorker; @Resource StringRedisTemplate stringRedisTemplate; @Override public Result seckillVoucher (Long voucherId) { SeckillVoucher seckillVoucher = iSeckillVoucherService.getById(voucherId); LocalDateTime beginTime = seckillVoucher.getBeginTime(); LocalDateTime endTime = seckillVoucher.getEndTime(); LocalDateTime now = LocalDateTime.now(); if (now.isBefore(beginTime)) { return Result.fail("秒杀尚未开始,请耐心等待!秒杀开始时间:" + beginTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); } if (now.isAfter(endTime)) { return Result.fail("秒杀已经结束,感谢支持!" ); } Integer stock = seckillVoucher.getStock(); if (stock <= 0 ) { return Result.fail("商品已经售罄!" ); } Long userID = UserHolder.getUser().getId(); SimpleRedisLock simpleRedisLock = new SimpleRedisLock ("order:" + userID.toString().intern(), stringRedisTemplate); boolean flag = simpleRedisLock.tryLock(RedisConstants.LOCK_VOUVHER_ORDER_TTL); if (!flag) { return Result.fail("[秒杀优惠券]不允许重复下单!本秒杀业务一切解释器归ty公司所有" ); } Result result; try { Object o = AopContext.currentProxy(); IVoucherOrderService proxy = (IVoucherOrderService) o; return proxy.createVoucherOrder(voucherId); } finally { simpleRedisLock.unLock(); } } @Override @Transactional public Result createVoucherOrder (Long voucherId) { Long userID = UserHolder.getUser().getId(); Integer count = query().eq("user_id" , userID).eq("voucher_id" , voucherId).count(); if (count > 0 ) { return Result.fail("秒杀优惠券每人限购1张,感谢配合,本优惠券最终解释权归ty公司所有!" ); } boolean result = iSeckillVoucherService.update() .setSql("stock = stock - 1" ) .eq("voucher_id" , voucherId) .gt("stock" , 0 ) .update(); if (!result) { return Result.fail("商品已经售罄!" ); } long orderId = redisWorker.nextID("order" ); VoucherOrder voucherOrder = new VoucherOrder (); voucherOrder.setId(orderId); voucherOrder.setVoucherId(voucherId); voucherOrder.setUserId(userID); save(voucherOrder); return Result.ok(orderId); } }
1.6 锁误删问题 1.6.1 介绍 持有锁的线程在锁的内部出现了阻塞,导致他的锁自动释放。
这时其他线程,线程2来尝试获得锁,就拿到了这把锁,然后线程2在持有锁执行过程中,线程1反应过来,继续执行,而线程1执行过程中,走到了删除锁逻辑,此时就会把本应该属于线程2的锁进行删除。
1.6.2 解决方案 在获取锁之前,增加一个标识,每次删除锁的时候,判断当前的锁是否属于自己,属于自己就删除,不属于自己就不删除。
步骤:
在获取锁时存入线程标示(可以用UUID表示)
在释放锁时先获取锁中的线程标示,判断是否与当前线程标示一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public class SimpleRedisLock implements ILock { private static final String KEY_PREFIX = "lock:" ; private static final String THREAD_ID_PREFIX = UUID.randomUUID().toString(true ) + "-" ; private String lockName; StringRedisTemplate stringRedisTemplate; public SimpleRedisLock (String lockName, StringRedisTemplate stringRedisTemplate) { this .lockName = lockName; this .stringRedisTemplate = stringRedisTemplate; } @Override public boolean tryLock (long expireTime) { String threadId = THREAD_ID_PREFIX + Thread.currentThread().getId(); Boolean flag = stringRedisTemplate.opsForValue() .setIfAbsent(KEY_PREFIX + lockName, threadId + "" , expireTime, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } @Override public void unLock () { String threadId = THREAD_ID_PREFIX + Thread.currentThread().getId(); String threadId_Redis = stringRedisTemplate.opsForValue().get(KEY_PREFIX + lockName); if (String.valueOf(threadId).equals(threadId_Redis)) { stringRedisTemplate.delete(KEY_PREFIX + lockName); } } }
1.7 分布式锁原子性操作问题 1.7.1 介绍 在上述部分绝大部分情况都能满足业务生产使用,但是在最后判断和删除的时候还是有可能出现阻塞,例如:判断完属于自己的锁,正准备删除的时候,此时应用出现full gc,这个时候就出现了阻塞,同理,其他线程此时由于线程1的阻塞导致的释放锁而拿到了锁,线程1等阻塞完成之后还是会删除锁
1.7.2 解决方案 Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言,它的基本语法大家可以参考网站:菜鸟教程 。
1.7.2.1 更改释放锁逻辑 原逻辑:
获取锁中的线程标示
判断是否与指定的标示(当前线程标示)一致
如果一致则释放锁(删除)
如果不一致则什么都不做
使用Lua脚本更改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 local key = KEYS[1 ]local threadID = ARGV[1 ]local threadID_Redis = redis.call('get' ,KEYS[1 ])if (ARGV[1 ] == threadID_Redis) then redis.call('del' ,KEYS[1 ])end return 0
java代码调用Lua脚本,更改分布式锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class SimpleRedisLock implements ILock { private static final String KEY_PREFIX = "lock:" ; private static final String THREAD_ID_PREFIX = UUID.randomUUID().toString(true ) + "-" ; private String lockName; StringRedisTemplate stringRedisTemplate; private static final DefaultRedisScript<Long> UNLOCK_SCRIPT; static { UNLOCK_SCRIPT = new DefaultRedisScript <>(); UNLOCK_SCRIPT.setLocation(new ClassPathResource ("unlock.lua" )); UNLOCK_SCRIPT.setResultType(Long.class); } public SimpleRedisLock (String lockName, StringRedisTemplate stringRedisTemplate) { this .lockName = lockName; this .stringRedisTemplate = stringRedisTemplate; } @Override public void unLock () { stringRedisTemplate.execute( UNLOCK_SCRIPT, Collections.singletonList(KEY_PREFIX + lockName), THREAD_ID_PREFIX + Thread.currentThread().getId() ); } }
1.8 Redisssion分布式锁 使用setnx实现分布式锁存在以下问题:
重入问题:重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都是使用synchronized修饰的,假如他在一个方法内,调用另一个方法,那么此时如果是不可重入的,不就死锁了吗?所以可重入锁他的主要意义是防止死锁,我们的synchronized和Lock锁都是可重入的。
不可重试:是指目前的分布式只能尝试一次,我们认为合理的情况是:当线程在获得锁失败后,他应该能再次尝试获得锁。
超时释放:我们在加锁时增加了过期时间,这样的我们可以防止死锁,但是如果卡顿的时间超长,虽然我们采用了lua表达式防止删锁的时候,误删别人的锁,但是毕竟没有锁住,有安全隐患。
主从一致性: 如果Redis提供了主从集群,当我们向集群写数据时,主机需要异步的将数据同步给从机,而万一在同步过去之前,主机宕机了,就会出现死锁问题。
1.8.1 关于Redission Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
官网:Redisson官网
1.8.2 引入pom依赖 1 2 3 4 5 <dependency > <groupId > org.redisson</groupId > <artifactId > redisson</artifactId > <version > 3.13.6</version > </dependency >
1.8.3 增加配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration public class RedissonConfig { @Bean public RedissonClient redissonClient () { Config config = new Config (); config.useSingleServer().setAddress("redis://192.168.150.101:6379" ) .setPassword("123321" ); return Redisson.create(config); } }
1.8.4 使用Redission解决一人一单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 @Service public class VoucherOrderServiceImpl extends ServiceImpl <VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Autowired private ISeckillVoucherService iSeckillVoucherService; @Autowired private RedisWorker redisWorker; @Resource StringRedisTemplate stringRedisTemplate; @Resource private RedissonClient redissonClient; @Override public Result seckillVoucher (Long voucherId) { SeckillVoucher seckillVoucher = iSeckillVoucherService.getById(voucherId); LocalDateTime beginTime = seckillVoucher.getBeginTime(); LocalDateTime endTime = seckillVoucher.getEndTime(); LocalDateTime now = LocalDateTime.now(); if (now.isBefore(beginTime)) { return Result.fail("秒杀尚未开始,请耐心等待!秒杀开始时间:" + beginTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); } if (now.isAfter(endTime)) { return Result.fail("秒杀已经结束,感谢支持!" ); } Integer stock = seckillVoucher.getStock(); if (stock <= 0 ) { return Result.fail("商品已经售罄!" ); } Long userID = UserHolder.getUser().getId(); RLock lock = redissonClient.getLock("lock:order:" + userID.toString().intern()); boolean flag = lock.tryLock(); if (!flag) { return Result.fail("[秒杀优惠券]不允许重复下单!本秒杀业务一切解释器归ty公司所有" ); } try { Object o = AopContext.currentProxy(); IVoucherOrderService proxy = (IVoucherOrderService) o; return proxy.createVoucherOrder(voucherId); } finally { lock.unlock(); } } }
1.8.5 Redission的可重入锁原理 在JDK中,lock锁和synchronized锁都是可重入锁,可重入锁就是当前一个线程在调就是一个线程不用释放,可以重复的获取一个锁n次,只是在释放的时候,也需要相应的释放n次。
那么猜想Redisssion可以实现可重入就一定有一个记录获取锁次数的变量。
1.8.5.1 Redis无法实现可重入锁
可以看到没有一个变量用来计数锁获取次数
1.8.5.2 Redisssion可实现可重入锁
1.8.5.3 Lua脚本实现获取锁(可重入)
1.8.5.4 Lua脚本实现释放锁(可重入)
1.8.5.5 测试代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @SpringBootTest @Slf4j public class RedissonTest { @Autowired private RedissonClient redissonClient; private RLock lock; @BeforeEach void setup () { lock=redissonClient.getLock("order" ); } @Test void method1 () { boolean isLock = lock.tryLock(); if (!isLock){ log.error("获取锁失败……1" ); return ; } try { log.info("获取锁成功……1" ); method2(); log.info("开始执行业务……1" ); }finally { log.warn("准备释放锁……1" ); lock.unlock(); } } void method2 () { boolean isLock = lock.tryLock(); if (!isLock){ log.error("获取锁失败……2" ); return ; } try { log.info("获取锁成功……2" ); log.info("开始执行业务……2" ); }finally { log.warn("准备释放锁……2" ); lock.unlock(); } } }
1.8.5.6 源码 tryLock():
unLock():
注:在最后删除锁之后,还进行了一个发布动作
1.8.6 Redission的锁重试和看门狗机制 1.8.6.1 获取锁重试 redis获取锁只尝试一次就返回false,没有更多的尝试机制。
整体流程:
同时计算获取锁耗时,再使用等待时间-获取锁耗时时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 public boolean tryLock (long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { long time = unit.toMillis(waitTime); long current = System.currentTimeMillis(); long threadId = Thread.currentThread().getId(); Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId); if (ttl == null ) { return true ; } time -= System.currentTimeMillis() - current; if (time <= 0 ) { acquireFailed(waitTime, unit, threadId); return false ; } current = System.currentTimeMillis(); CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId); try { subscribeFuture.get(time, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { if (!subscribeFuture.completeExceptionally(new RedisTimeoutException ( "Unable to acquire subscription lock after " + time + "ms. " + "Try to increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters." ))) { subscribeFuture.whenComplete((res, ex) -> { if (ex == null ) { unsubscribe(res, threadId); } }); } acquireFailed(waitTime, unit, threadId); return false ; } catch (ExecutionException e) { acquireFailed(waitTime, unit, threadId); return false ; } try { time -= System.currentTimeMillis() - current; if (time <= 0 ) { acquireFailed(waitTime, unit, threadId); return false ; } while (true ) { long currentTime = System.currentTimeMillis(); ttl = tryAcquire(waitTime, leaseTime, unit, threadId); if (ttl == null ) { return true ; } time -= System.currentTimeMillis() - currentTime; if (time <= 0 ) { acquireFailed(waitTime, unit, threadId); return false ; } currentTime = System.currentTimeMillis(); if (ttl >= 0 && ttl < time) { commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } else { commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS); } time -= System.currentTimeMillis() - currentTime; if (time <= 0 ) { acquireFailed(waitTime, unit, threadId); return false ; } } } finally { unsubscribe(commandExecutor.getNow(subscribeFuture), threadId); } }
1.8.6.2 看门狗机制(刷新有效期) 1.8.6.2.1 获取锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private <T> RFuture<Long> tryAcquireAsync (long waitTime, long leaseTime, TimeUnit unit, long threadId) { RFuture<Long> ttlRemainingFuture; if (leaseTime > 0 ) { ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } else { ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); } CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> { if (ttlRemaining == null ) { if (leaseTime > 0 ) { internalLockLeaseTime = unit.toMillis(leaseTime); } else { scheduleExpirationRenewal(threadId); } } return ttlRemaining; }); return new CompletableFutureWrapper <>(f); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 protected void scheduleExpirationRenewal (long threadId) { ExpirationEntry entry = new ExpirationEntry (); ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry); if (oldEntry != null ) { oldEntry.addThreadId(threadId); } else { entry.addThreadId(threadId); try { renewExpiration(); } finally { if (Thread.currentThread().isInterrupted()) { cancelExpirationRenewal(threadId); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 private void renewExpiration () { ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null ) { return ; } Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask () { @Override public void run (Timeout timeout) throws Exception { ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ent == null ) { return ; } Long threadId = ent.getFirstThreadId(); if (threadId == null ) { return ; } CompletionStage<Boolean> future = renewExpirationAsync(threadId); future.whenComplete((res, e) -> { if (e != null ) { log.error("Can't update lock {} expiration" , getRawName(), e); EXPIRATION_RENEWAL_MAP.remove(getEntryName()); return ; } if (res) { renewExpiration(); } else { cancelExpirationRenewal(null ); } }); } }, internalLockLeaseTime / 3 , TimeUnit.MILLISECONDS); ee.setTimeout(task); }
1 2 3 4 5 6 7 8 9 10 protected CompletionStage<Boolean> renewExpirationAsync (long threadId) { return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return 1; " + "end; " + "return 0;" , Collections.singletonList(getRawName()), internalLockLeaseTime, getLockName(threadId)); }
1.8.6.2.2 释放锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public RFuture<Void> unlockAsync (long threadId) { RFuture<Boolean> future = unlockInnerAsync(threadId); CompletionStage<Void> f = future.handle((opStatus, e) -> { cancelExpirationRenewal(threadId); if (e != null ) { throw new CompletionException (e); } if (opStatus == null ) { IllegalMonitorStateException cause = new IllegalMonitorStateException ("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + threadId); throw new CompletionException (cause); } return null ; }); return new CompletableFutureWrapper <>(f); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 protected void cancelExpirationRenewal (Long threadId) { ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (task == null ) { return ; } if (threadId != null ) { task.removeThreadId(threadId); } if (threadId == null || task.hasNoThreads()) { Timeout timeout = task.getTimeout(); if (timeout != null ) { timeout.cancel(); } EXPIRATION_RENEWAL_MAP.remove(getEntryName()); } }
1.8.7 Redission的multiLock问题 1.8.7.1 背景 Redisson分布式锁主从一致性问题 为了提高redis的可用性,我们会搭建集群或者主从,现在以主从为例 此时我们去写命令,写在主机上, 主机会将数据同步给从机,但是假设在主机还没有来得及把数据写入到从机去的时候,此时主机宕机,哨兵会发现主机宕机,并且选举一个slave变成master,而此时新的master中实际上并没有锁信息,此时锁信息就已经丢掉了。
1.8.7.2 解决 redission提出来了MutiLock锁,使用这把锁咱们就不使用主从了,每个节点的地位都是一样的, 这把锁加锁的逻辑需要写入到每一个主丛节点上,只有所有的服务器都写入成功,此时才是加锁成功,假设现在某个节点挂了,那么他去获得锁的时候,只要有一个节点拿不到,都不能算是加锁成功,就保证了加锁的可靠性。
1.8.7.3 使用示例 修改配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Configuration public class RedissonConfig { @Bean public RedissonClient redissonClient () { Config config = new Config (); config.useSingleServer().setAddress("redis://192.168.183.145:6379" ) .setPassword("112453" ); return Redisson.create(config); } @Bean public RedissonClient redissonClient2 () { Config config = new Config (); config.useSingleServer().setAddress("redis://192.168.193.175:6380" ) .setPassword("557724" ); return Redisson.create(config); } @Bean public RedissonClient redissonClient3 () { Config config = new Config (); config.useSingleServer().setAddress("redis://192.168.177.145:6381" ) .setPassword("5896" ); return Redisson.create(config); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 @Slf4j @SpringBootTest public class RedissonTest { @Resource private RedissonClient redissonClient; @Resource private RedissonClient redissonClient2; @Resource private RedissonClient redissonClient3; RLock lock; @BeforeEach void setUp () { RLock lock1 = redissonClient.getLock("lock" ); RLock lock2 = redissonClient.getLock("lock" ); RLock lock3 = redissonClient.getLock("lock" ); lock = redissonClient.getMultiLock(lock1, lock2, lock3); } @Test void method1 () { boolean isLock = lock.tryLock(); if (!isLock) { log.error("获取锁失败1" ); return ; } try { log.info("获取锁成功,1" ); method2(); } finally { log.info("释放锁,1" ); lock.unlock(); } } @Test void method2 () { boolean isLock = lock.tryLock(); if (!isLock) { log.error("获取锁失败2" ); return ; } try { log.info("获取锁成功,2" ); } finally { log.info("释放锁,2" ); lock.unlock(); } } }
1.8.7.4 源码 getMultiLock()
1 2 3 4 5 6 7 8 9 10 11 12 final List<RLock> locks = new ArrayList <>();@Override public RLock getMultiLock (RLock... locks) { return new RedissonMultiLock (locks); }public RedissonMultiLock (RLock... locks) { if (locks.length == 0 ) { throw new IllegalArgumentException ("Lock objects are not defined" ); } this .locks.addAll(Arrays.asList(locks)); }
加锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public boolean tryLock (long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { long newLeaseTime = -1 ; if (leaseTime != -1 ) { newLeaseTime = unit.toMillis(waitTime)*2 ; } long time = System.currentTimeMillis(); long remainTime = -1 ; if (waitTime != -1 ) { remainTime = unit.toMillis(waitTime); } long lockWaitTime = calcLockWaitTime(remainTime); int failedLocksLimit = failedLocksLimit(); List<RLock> acquiredLocks = new ArrayList <>(locks.size()); for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) { RLock lock = iterator.next(); boolean lockAcquired; try { if (waitTime == -1 && leaseTime == -1 ) { lockAcquired = lock.tryLock(); } else { long awaitTime = Math.min(lockWaitTime, remainTime); lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS); } } catch (RedisResponseTimeoutException e) { unlockInner(Arrays.asList(lock)); lockAcquired = false ; } catch (Exception e) { lockAcquired = false ; } if (lockAcquired) { acquiredLocks.add(lock); } else { if (locks.size() - acquiredLocks.size() == failedLocksLimit()) { break ; } if (failedLocksLimit == 0 ) { unlockInner(acquiredLocks); if (waitTime == -1 && leaseTime == -1 ) { return false ; } failedLocksLimit = failedLocksLimit(); acquiredLocks.clear(); while (iterator.hasPrevious()) { iterator.previous(); } } else { failedLocksLimit--; } } if (remainTime != -1 ) { remainTime -= System.currentTimeMillis() - time; time = System.currentTimeMillis(); if (remainTime <= 0 ) { unlockInner(acquiredLocks); return false ; } } } if (leaseTime != -1 ) { List<RFuture<Boolean>> futures = new ArrayList <>(acquiredLocks.size()); for (RLock rLock : acquiredLocks) { RFuture<Boolean> future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS); futures.add(future); } for (RFuture<Boolean> rFuture : futures) { rFuture.syncUninterruptibly(); } } return true ; }