一:redisTemplate.opsForValue().setIfAbsent问题
1.1 说明
| 12
 3
 4
 5
 6
 7
 
 | 当自己直接使用redisTemplate.opsForValue().setIfAbsent进行获取锁的方法,当并发量不是很大的时候,这里的还是能使用的,不过一般作为生产
 的分布式锁不建议这么使用,一旦超高并发就会存在各种问题。主要是考虑
 如下几点:
 1.设置锁超时时间
 2.设置clientId,保证谁锁定的谁去释放
 3.finally 确保可以释放锁
 
 | 
1.2 测试代码
| 12
 3
 
 | 如何代码在超高并发的时候也是存在问题的,如果在timeout时间外第一个线程尚未执行完毕,第二个线程拿到锁以后就可以继续往后面执行,如果timeout设置
 的过长,如果一些特殊原因,比如应用重启等会导致一直被锁定。
 
 | 
| 12
 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
 
 | package com.tohours.miniprogram.controller;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import com.tohours.miniprogram.exception.TohoursException;
 import com.tohours.miniprogram.support.TohoursResponse;
 import com.tohours.miniprogram.support.TohoursResult;
 
 import lombok.extern.slf4j.Slf4j;
 
 /**
 * @desc redis相关
 * @author RenJie
 * @date 2023-02-27
 *
 */
 @RestController
 @RequestMapping("/redis")
 @SuppressWarnings("rawtypes")
 @Slf4j
 public class RedisController extends BaseController {
 
 @Autowired
 private StringRedisTemplate stringRedisTemplate;
 private Boolean isFirst = true;
 
 /**
 * @desc redis lock测试
 * @return
 */
 @RequestMapping(value = "testLock")
 public TohoursResult<Object> testLock() {
 
 String prizeNumKey = "prizeNum1";
 String lockKey = "prize_1";
 String clientId = UUID.randomUUID().toString();
 Long timeout = 30L;
 // 获取锁
 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, timeout, TimeUnit.SECONDS);
 if (!lock) {// 未获取到锁
 log.info("未获取到锁,结束");
 throw new TohoursException("未获取到锁,结束");
 }
 try {
 String tmp = stringRedisTemplate.opsForValue().get(prizeNumKey);
 if (isFirst) {
 tmp = "300";
 isFirst = false;
 }
 int prize1Num = Integer.parseInt(tmp);
 if (prize1Num > 0) {
 prize1Num--;
 stringRedisTemplate.opsForValue().set(prizeNumKey, String.valueOf(prize1Num));
 log.info(Thread.currentThread().getName() + "剩余数量:" + prize1Num);
 }
 
 } finally {
 // 释放锁
 String cacheClientId = stringRedisTemplate.opsForValue().get(lockKey);
 if (clientId.equals(cacheClientId)) {// 谁锁定得谁释放
 stringRedisTemplate.delete(lockKey);
 }
 }
 return TohoursResponse.success();
 }
 
 }
 
 
 | 
二:使用redssion做分布式锁
2.1 介绍
| 12
 3
 4
 5
 6
 
 | redis自动2.6版本以后支持了Lua语言,redission是在redis基础操作的基础上,做了超时后自动延迟超时时间,并把基础的超时时间设置,线程ID设置,释放锁
 等都统一做了处理,所以代码里面我们只要引入使用即可。
 
 如果线程A获取到了锁,正在执行任务,尚未释放锁,此时线程B也去获取锁,
 如果获取锁失败,则会等待锁的释放。
 
 | 
2.2 springboot引入redssion
| 12
 3
 4
 5
 
 | <dependency><groupId>org.redisson</groupId>
 <artifactId>redisson</artifactId>
 <version>3.19.3</version>
 </dependency>
 
 | 
| 1
 | 在基于引入redis的基础上做的配置,此距离是单例模式
 | 
| 12
 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
 
 | package com.tohours.miniprogram.config;
 import org.redisson.Redisson;
 import org.redisson.api.RedissonClient;
 import org.redisson.config.Config;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 /**
 * @desc redisson bean管理
 * @author RenJie
 * @date 2023-02-28
 */
 @Configuration
 public class RedissonConfig {
 
 @Value("${spring.redis.host}")
 private String host;
 
 @Value("${spring.redis.port}")
 private String port;
 
 @Value("${spring.redis.password}")
 private String password;
 
 @Bean
 public RedissonClient getRedisson() {
 Config config = new Config();
 config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
 // 添加主从配置
 //	        config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
 return Redisson.create(config);
 }
 }
 
 | 
2.3 使用redisson分布式锁
| 12
 3
 4
 
 | 1.redisson首先获取lock实例2.加锁
 3.释放锁
 4.当线程未获取到锁的时候会等待
 
 | 
| 12
 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
 
 | package com.tohours.miniprogram.controller;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import com.tohours.miniprogram.exception.TohoursException;
 import com.tohours.miniprogram.support.TohoursResponse;
 import com.tohours.miniprogram.support.TohoursResult;
 
 import lombok.extern.slf4j.Slf4j;
 
 /**
 * @desc redis相关
 * @author RenJie
 * @date 2023-02-27
 *
 */
 @RestController
 @RequestMapping("/redis")
 @SuppressWarnings("rawtypes")
 @Slf4j
 public class RedisController extends BaseController {
 
 @Autowired
 private StringRedisTemplate stringRedisTemplate;
 private Boolean isFirst = true;
 
 /**
 * @desc redis lock测试
 * @return
 */
 @RequestMapping(value = "testLock")
 public TohoursResult<Object> testLock() {
 
 String prizeNumKey = "prizeNum1";
 String lockKey = "prize_1";
 String clientId = UUID.randomUUID().toString();
 Long timeout = 30L;
 // 获取锁
 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, timeout, TimeUnit.SECONDS);
 if (!lock) {// 未获取到锁
 log.info("未获取到锁,结束");
 throw new TohoursException("未获取到锁,结束");
 }
 try {
 String tmp = stringRedisTemplate.opsForValue().get(prizeNumKey);
 if (isFirst) {
 tmp = "300";
 isFirst = false;
 }
 int prize1Num = Integer.parseInt(tmp);
 if (prize1Num > 0) {
 prize1Num--;
 stringRedisTemplate.opsForValue().set(prizeNumKey, String.valueOf(prize1Num));
 log.info(Thread.currentThread().getName() + "剩余数量:" + prize1Num);
 }
 
 } finally {
 // 释放锁
 String cacheClientId = stringRedisTemplate.opsForValue().get(lockKey);
 if (clientId.equals(cacheClientId)) {// 谁锁定得谁释放
 stringRedisTemplate.delete(lockKey);
 }
 }
 return TohoursResponse.success();
 }
 
 @Autowired
 private RedissonClient resissonClient;
 
 /**
 * @desc redisson Lock测试
 * @return
 */
 @RequestMapping(value = "redissonLock")
 public TohoursResult<Object> redissonLock() {
 
 String prizeNumKey = "prizeNum1";
 String lockKey = "prize_1";
 // 获取锁
 RLock redissonLock = resissonClient.getLock(lockKey);
 redissonLock.lock();// 锁定
 try {
 String tmp = stringRedisTemplate.opsForValue().get(prizeNumKey);
 if (isFirst) {
 tmp = "300";
 isFirst = false;
 }
 int prize1Num = Integer.parseInt(tmp);
 if (prize1Num > 0) {
 prize1Num--;
 stringRedisTemplate.opsForValue().set(prizeNumKey, String.valueOf(prize1Num));
 log.info(Thread.currentThread().getName() + "剩余数量:" + prize1Num);
 }
 
 } finally {
 // 释放锁
 redissonLock.unlock();
 }
 return TohoursResponse.success();
 }
 
 }
 
 | 
2.4 读写锁
| 12
 3
 
 | 读写锁: 保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共享锁。 写锁没释放,读锁就必须等待。
 针对读数据时,只要当前正在修改数据,读数据就会等待写完成后,拿到最新数据。
 
 | 
| 12
 3
 4
 5
 
 | 读 + 读 :相当于无所 n请求可以同时加锁成功 redis中会记录着进来锁的状态写 + 读 :等待写锁释放 才能读到新数据(读被阻塞)
 写 + 写 : 阻塞方式(后面写锁等待前面写锁释放)
 读 + 写 :有读锁 写也需要等待(写锁:等你读完这一次 我再改写)
 只要有写锁的存在,都必须等待。
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | //获取读写锁RReadWriteLock readWriteLock = resissonClient.getReadWriteLock(lockKey);
 //获取读锁
 RLock rLock = readWriteLock.readLock();
 //加锁
 rLock.lock();
 //释放锁
 rLock.unlock();
 //获取写锁
 rLock = readWriteLock.writeLock();
 //加锁
 rLock.lock();
 //释放锁
 rLock.unlock();
 
 | 
 
  
  
     *************感谢您的阅读*************