本笔记基于redisson依赖,springBoot项目环境建议使用redisson-spring-boot-starter依赖。redisson锁实现同juc包中的锁。
引入依赖
真实项目中推荐redisson-spring-boot-starter。我这里使用纯redisson依赖。
1 2 3 4 5
| <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.22.1</version> </dependency>
|
配置redisson
新建MyRedissonConfig文件,此文件从nacos中的配置获取redis地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Configuration @RefreshScope public class MyRedissonConfig {
@Value("${redis.address}") private String redisAddress;
@Bean(destroyMethod = "shutdown") public RedissonClient redisson() { Config config = new Config(); config.useSingleServer() .setAddress("redis://" + redisAddress); return Redisson.create(config); } }
|
redis的地址是配置在nacos中动态获取的。见上面配置类注释。
1 2
| redis: address: 127.0.0.1:6379
|
redisson使用
redisson是根据锁的名字来区分是否同一把锁的。
通过lock.lock(10, TimeUnit.SECONDS)
方法上锁,此方法可以传参过期时间,表示时间到了之后会自动释放锁,(或者手动释放锁)。即使任务没有执行完毕,锁依旧会被释放。后续在尝试手动释放锁的时候会报锁不存在错误。
若不传时间参数lock.lock()
。则锁的过期时间为30s。并且redisson的看门狗会在经过10s后自动给锁续期为30s。直到主动释放锁。假设出现硬件故障(如断电)导致程序问题主动锁释放锁失败,此时由于程序问题,看门狗不会再续期,因此时间到之后,redis中的锁会自动过期。保证了不会由于硬件问题导致的死锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @ResponseBody @GetMapping("/hello") public String hello() { RLock lock = redisson.getLock("my-lock"); lock.lock(); try { System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId()); Thread.sleep(10000L); } catch (Exception e) { System.out.println(e.getMessage()); } finally { System.out.println("释放锁..." + Thread.currentThread().getId()); lock.unlock(); }
return "hello"; }
|
redisson读写锁
读写锁用于读多写少的并发情况。(读多写少的数据如果不要求强一致性,只要求最终一致性,非常适合放入redis中。注意,此段括号内容说的是数据存入缓存。锁还是要的)。
读写锁互斥情况:
- 读、读:不互斥,可以并发
- 读、写:互斥,只要读锁没有释放,持有写锁的就得等待
- 写、读:互斥,只要写锁没有释放,持有读锁的就得等待
- 写、写:互斥,只要写锁没有释放,尝试获取写锁就得等待。
测试代码改数据加写锁、读数据加读锁。write接口往redis中写数据,read接口从redis中读数据
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
| @GetMapping("/write") @ResponseBody public String writeValue() { RReadWriteLock lock = redisson.getReadWriteLock("rw-lock"); String s = ""; RLock rLock = lock.writeLock(); rLock.lock(); try { s = UUID.randomUUID().toString(); Thread.sleep(3000); stringRedisTemplate.opsForValue().set("writevlue", s); } catch (InterruptedException e) { e.printStackTrace(); } finally { rLock.unlock(); } return s; }
@GetMapping("/read") @ResponseBody public String readValue() { RReadWriteLock lock = redisson.getReadWriteLock("rw-lock"); String s = ""; RLock rLock = lock.readLock(); rLock.lock(); try { s = stringRedisTemplate.opsForValue().get("writevlue"); } catch (Exception e) { e.printStackTrace(); } finally { rLock.unlock(); } return s; }
|
redisson信号量
同juc中的Semaphore,一般用于限制流量(如果需要限流可以使用专业的限流中间件,如GitHub - alibaba/Sentinel (面向云原生微服务的高可用流控防护组件)或 GitHub - Netflix/Hystrix)。初始化Semaphore有多少个资源。每次场次acquire会将资源减1,直到资源变为0, 此时其他想要acquire的会阻塞。直到有对象release信号量,信号量+1,才能被其他尝试acquire的获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @GetMapping("/park") @ResponseBody public String park() { RSemaphore park = redissonClient.getSemaphore("park"); try { park.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } return "停进"; }
@GetMapping("/go") @ResponseBody public String go() { RSemaphore park = redissonClient.getSemaphore("park"); park.release(); return "开走"; }
|
countDownLatch闭锁
同juc的countDownLatch,调用await()方法的会阻塞,直到countDown被减为0。才会执行,一般用于使多个线程的任务全部完成后,在统一处理后续操作。
1 2 3 4 5 6 7 8 9 10 11
| RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
doSomething(); latch.countDown();
|