/**
* <br>
* <br>
*
*
* @date 2022-2-18 14:53
*/
@RestController
public class RedissonController {
@Resource
StringRedisTemplate stringRedisTemplate;
@Resource
Redisson redisson;
/**
* 一段代码看出redis做锁的问题,为什么要用redisson
* stringRedisTemplate.opsForValue().setIfAbsent()
* 这段代码的底层用的是setnex的命令,此命令的特性就是,如果在相同key的情况下,你设置了第一个key成功返回true之后,第二次再设置就会返回false不成功
* 问题:高并发的情况下
* 1.
* Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId);
* 在这段代码运行的时候,还没有运行到stringRedisTemplate.delete(lockKey)删除锁的时候,中途服务宕机了就会造成死锁
*
*2.当出现死锁的情况下加一段代码
* stringRedisTemplate.expire(lockKey,10, TimeUnit.SECONDS);
* 设置redis的key的失效时间,引发的问题:
* 高并发情况下,第一条线程还没走到设置超时时间的时候,突然服务就挂了,还是会出现死锁
* 所以设置锁和失效时间必须要有原子性(合并代码)解决:
* Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
* 这样还是会引发问题:
* 高并发情况下,如果第一条线程进来了,还没走到删除哪里就已经超过10秒了,停顿了等待恢复,那么第二条线程进来了,就占用了锁,然后第一条线程突然恢复过来,执行了删除锁的逻辑
* 那么这个时候删除的就是第二条线程的锁了,就会造成永久死锁
* 解决方案:再finally模块做一个判断逻辑删除:
* if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
* stringRedisTemplate.delete(lockKey);
* }
* 此时还会引发出一个问题:
* 在这段代码clientId.equals(stringRedisTemplate.opsForValue().get(lockKey)执行后,突然服务出现卡顿,超过了10秒,那么锁又失效了,第二条线程进来又抢到了锁,突然第一条
* 线程又开始执行了stringRedisTemplate.delete(lockKey); 这个时候删除的就是第二条线程的锁,还是会出现上面的问题;
* 解决方案: 超时时间做续期
* 当第一条线程进来的时候,开分线程,开一个定时器,分线程每过一段时间查询一下这条主线程是否还拥有这把锁,如果还拥有,那么就给这把锁的失效时间做一个续期
*
* 因为实现麻烦,所以引入框架,redisson
*/
@RequestMapping("/redisLock")
public String redissonLock(){
String lockKey="xxx";//一般用订单id+前缀或者后缀做锁的key
//做value
String clientId = UUID.randomUUID().toString();
//Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId);
//stringRedisTemplate.expire(lockKey,10, TimeUnit.SECONDS);
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
if (!result){
return "errorCode";
}
try{
//获取库存
int stock= Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock>0){
int realStock=stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock+"");
System.out.println("扣减成功,剩余库存:"+realStock);
}else {
System.out.println("扣减失败,库存不足");
}
}finally {
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
/**
* 引入redisson框架
*
* 底层实现,lua脚本执行,执行后会有一个分线程,用的是定时调度线程池,底层也是执行lua脚本,首先判断主线程的key是否占用了锁,如果占用到锁,那么就重新给锁的过期时间续费,如果没有就不执行
*
* 在此框架中,实际就是将并发线程串行起来的,又会导致性能问题产生
* 解决方案:分段锁
* 比如 库存有200个,我分成10个段,那么再分配到redis的集群中去,通过负载均衡算法去拿,就提高了性能
*
*
*
*
* @return
*/
@RequestMapping("/redisLockssss")
public String redisisonLock(){
String lockKey="xxx";//一般用订单id+前缀或者后缀做锁的key
RLock redissonLock = redisson.getLock(lockKey);
try{
redissonLock.lock();
//获取库存
int stock= Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock>0){
int realStock=stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock+"");
System.out.println("扣减成功,剩余库存:"+realStock);
}else {
System.out.println("扣减失败,库存不足");
}
}finally {
redissonLock.unlock();
}
return "end";
}
/**
*
* redis的过期键的删除策略:
* 惰性过期:有点像懒加载的模式(按需加载),只有当你访问一个key的时候,才会去判断该Key是否过期,过期则清除,最大化的节省了cpu资源,极端情况下可能大量的key没有被再次访问,占用大量内存
*
* 定期过期:每隔一定的时间,会去扫描一定数量的数据库的expires字典中一定数据的key,并清除已过期的key
*
*/
/**
* redis的线程模型,为什么单线程快:
*
* 1.纯内存操作
* 2.核心是基于非阻塞的IO多路复用机制
* 3.单线程反而避免了多线程的繁复上下文切换带来的性能问题
*
* =================================================
*
*项目中应用锁得机制:
* 可重入锁:表示第一条线程和第二条线程并发进入得啥时候,抢到锁得那个线程如第一条线程,哪怕他执行完了后,还是可以重新进入,可重试
* 首先tryLock内部封装的是setnex 如果没有,那么就set进去一条值
*
*
* 参数1:自定义key值 以下方法是自定义的key,表示锁整个方法
* 参数2: redission tryLock 尝试获取锁的时间 :表示第二条线程进入尝试获取锁时间,若时间过去了,则抛出线程终端异常
* 参数3:redission tryLock 锁持有时间 :表示线程获取到锁,执行业务时长,如果超过了,那么就会自动释放锁,第二条线程即可进入
* 参数4:单位时间
*
* lockHelper.tryLock(AppConstant.KIRIN_SALES_CARPACKAGE_REDIS_KEY, AppConstant.WAIT_TIME, AppConstant.LEASE_TIME, TimeUnit.SECONDS)
*
*/
/* @Transactional(rollbackFor = Exception.class)
public Boolean saveCarPackage(Object obj) {
try {
if (lockHelper.tryLock(AppConstant.KIRIN_SALES_CARPACKAGE_REDIS_KEY, AppConstant.WAIT_TIME, AppConstant.LEASE_TIME, TimeUnit.SECONDS)) {
//业务逻辑
} catch (InterruptedException e) {
log.error("获取锁失败:", e);
Thread.currentThread().interrupt();
throw new BusinessException("服务异常,请重新再试");
} catch (BusinessException ex) {
log.error("业务异常:", ex);
throw new BusinessException(ex.getCode(), ex.getMessage());
} finally {
if (lockHelper.isLocked(AppConstant.KIRIN_SALES_CARPACKAGE_REDIS_KEY)) {
lockHelper.unlock(AppConstant.KIRIN_SALES_CARPACKAGE_REDIS_KEY);
}
}
return false;
}*/
/**
* 项目中第二种锁机制
* 可重入锁:表示线程获取到锁之后,哪怕执行完后,也可以重新进入锁机制:可重试
* tryLock()内部封装的是setnex表示如果没有这个key那么就set进去一个,如果有就不能set了
*
* 方法参数:
* 参数1:这里使用的是前缀加商品id:作用是区分每一个商品,如:两天线程拿着相同的商品id进入,第一条线程进入,第二条线程没有获取到锁等待尝试获取锁,当第一条线程跑完了,第二条进入,内部业务逻辑卡此商品id
*参数2:redission tryLock 尝试获取锁的时间 :表示第二条线程等待尝试获取锁的时长,若是时间过期,没有获取到,则抛出中断异常
* 参数3:redission tryLock 锁的持有时间:表示锁的执行业务时长,若是此线程时间到了还没有执行完成,那么就自动释放锁,让第二条线程进入
* 参数4:单位时间
*
*
* lockHelper.isLocked(lockId) :表示判断是否是属于这个key,防止删除其他线程
* lockHelper.unlock(lockId):释放此线程
*
*/
/* @Transactional(rollbackFor = Exception.class)
public Boolean executeOrderCallback(Object obj) {
String lockId = RedisPrefix.REDIS_LOCK_CREATE_DOWN_PREFIX + salesOrderCallbackVO.getSalesInfoId();
try {
if (lockHelper.tryLock(lockId, WAIT_TIME, LEASE_TIME, TimeUnit.SECONDS)) {
//执行业务方法
}
} catch (InterruptedException e) {
log.error("获取锁失败:" + salesOrderCallbackVO.getSalesInfoId(), e);
Thread.currentThread().interrupt();
throw new BusinessException("该单已被其他订单下定,无法再收定金");
} finally {
if (lockHelper.isLocked(lockId)) {
lockHelper.unlock(lockId);
}
}
return false;
}*/
/**
* Thread.currentThread().interrupt(); 表示做个可以中断线程的标记了
* 什么是中断:
* 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的机制——中断。
*
* 中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。若要中断一个线程,你需要手动调用该线程的interrupted方法,该方法也仅仅是将线程对象的中断标识设成true;
* 接着你需要自己写代码不断地检测当前线程的标识位;如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。
* 每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;
* 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
*
* 如何使用中断:
* 要使用中断,首先需要在可能会发生中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理代码。
* 当需要中断线程时,调用该线程对象的interrupt函数即可。
*/
/*
Thread thread = new Thread(() -> {
while (!Thread.interrupted()) {
// do more work.
}
});
thread.start();
// 一段时间以后
thread.interrupt();
*/
}
分布式锁,可重入锁机制
于 2022-03-18 18:44:37 首次发布