阶段一
代码如下:
private Map<String, List<CatelogTwoLevelVo>> getCatalogJsonFromDb() {
//得到锁之后,去缓存中再确定一次是否有数据
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
if (!StringUtils.isEmpty(catalogJson)) {//缓存中有数据
Map<String, List<CatelogTwoLevelVo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<CatelogTwoLevelVo>>>() {
});
return result;
}
//查询所有的一级分类
List<CategoryEntity> categoryEntities = this.selectOneLevelCategory();
//查询所有的二级分类和三级分类并封装数据
//map的key是一级分类的id,v是二级分类的vo
Map<String, List<CatelogTwoLevelVo>> map = categoryEntities.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//查询每一个一级分类下的二级分类
List<CategoryEntity> categoryTwoLevelEntities = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));
List<CatelogTwoLevelVo> collect = null;
if (categoryTwoLevelEntities != null) {
collect = categoryTwoLevelEntities.stream().map(l2 -> {
//查询该二级分类对应的三级分类并封装数据
List<CategoryEntity> categoryThreeLevelEntities = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
List<CatelogTwoLevelVo.CatalogThreeLevelVo> catalogThreeLevelVos = null;
if (categoryThreeLevelEntities != null) {
catalogThreeLevelVos = categoryThreeLevelEntities.stream().map(l3 -> {
CatelogTwoLevelVo.CatalogThreeLevelVo catalogThreeLevelVo = new CatelogTwoLevelVo.CatalogThreeLevelVo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
return catalogThreeLevelVo;
}).collect(Collectors.toList());
}
//构造二级分类的vo
CatelogTwoLevelVo catelogTwoLevelVo = new CatelogTwoLevelVo(v.getCatId().toString(), catalogThreeLevelVos, l2.getCatId().toString(), l2.getName());
return catelogTwoLevelVo;
}).collect(Collectors.toList());
}
return collect;
}));
String s = JSON.toJSONString(map);
int minutes = 1440 + new Random().nextInt(10);//过期时间:1天(1440分钟)加上随机数
redisTemplate.opsForValue().set("catalogJson", s, minutes, TimeUnit.MINUTES);//存入缓存
return map;
}
但是存在问题:如果一个线程在redis中站好了坑位,但是业务代码执行出现异常,有可能不会解锁,这就可能导致其余线程都没法获得锁,也就获取不到数据。即使我们处理异常,在finally中进行解锁,如果发生停电宕机,也会出现解锁不成功的情况
解决办法:给锁添加过期时间
阶段二
![](/i/l/?n=23&i=blog/2506674/202301/2506674-20230131200053613-740452810.png)
![](/i/l/?n=23&i=blog/2506674/202301/2506674-20230131200325675-2140988072.png)
但是存在问题:在我们设置过期时间之前突然宕机,又会陷入死循环,没有线程能获取锁
因此加锁和设置过期时间必须是一个原子操作;
阶段三
![](/i/l/?n=23&i=blog/2506674/202301/2506674-20230131200851164-2059903043.png)
![](/i/l/?n=23&i=blog/2506674/202301/2506674-20230131200931535-1357085494.png)
但是存在问题:如果我们的业务代码执行时间比较长,在我们删除锁之前,我们之前设置的锁已经自己过期了。现在redis中的锁是别的线程的锁,这样我们就删除了别人的锁。因此使用uuid区分每个线程的锁
阶段四
![](/i/l/?n=23&i=blog/2506674/202301/2506674-20230131202918120-1051633368.png)
![](/i/l/?n=23&i=blog/2506674/202301/2506674-20230131202949114-1182450533.png)
但是存在问题:我们删除锁是先获取值进行对比,然后对比成功才删除锁。如果我们对比成功了,恰好进入了if,但是此时我们的锁正好到时间过期了,别的进行又立刻占了一个锁,这时我们删除的锁就不是自己的锁。就会导致有一个线程抢到锁,即没锁住。因此,获取值+删除锁应该是一个原子操作
阶段五:最终形态
使用lua脚本来完成原子解锁
官方文档如下:
![](/i/l/?n=23&i=blog/2506674/202301/2506674-20230131205809368-771248935.png)
代码如下:
![](/i/l/?n=23&i=blog/2506674/202301/2506674-20230131210524012-1578072003.png)
分布式锁还有更专业框架,在下面文章中有记载