V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tiRolin
V2EX  ›  Java

我的这个加入组队帖子的方法如何改造成双重检查锁形式?

  •  
  •   tiRolin · 2023-01-06 21:33:07 +08:00 · 1455 次点击
    这是一个创建于 715 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我学长要求我在学校的小程序里做一个组队的功能,学生发帖可以发起组队贴,这样可以找到同好 下面是我写的方法的代码,学长希望将 synchronized 代码块改成双重检查锁的形式,我看了我以前的笔记,发现双重检查锁一般是用于单例模式中的,而不知道怎么将这个类改成单例模式的样子,实在不会了来问问

    @CrossOrigin
    @RestController
    @Slf4j
    @RequestMapping("/planet")
    public class TeamController implements Teamable {
    
        @Resource
        MongoTemplate mongoTemplate;
    
        @Autowired
        ZKCourseCalling courseCalling;
    
        private Runtime runtime;
    
        @Override
        @ApiOperation("加入组队的帖子")
        @PostMapping("/teamUpWith")
        public R teamUp(@RequestParam String planetId, @RequestParam("weChat") String weChat){
            String openId = UniversityInterceptor.getOpenId();
            R userInfo = courseCalling.getUserInfo(openId);
            Object data = userInfo.getData().get("userInfo");
            User user = JSON.parseObject(JSON.toJSONString(data), User.class);
    //        user = new User();
    //        user.setOpenid("test");
    //        user.setAvatarUrl("test");
            Planet planet = mongoTemplate.findById(planetId, Planet.class);
            if(planet==null){
                return R.error().message("没有找到该帖子");
            }
            Group group = planet.getGroup();
            if(group.getInNum().equals(group.getTotalNum())){
                return R.error().message("组队人数已满");
            }
            synchronized (this){
                Member member = new Member();
                member.setOpenId(user.getOpenid());
                member.setAvatarUrl(user.getAvatarUrl());
                member.setWechat(weChat);
                List<Member> memberList = group.getMemberList();
                memberList.add(member);
                group.setMemberList(memberList);
                group.setInNum(group.getInNum()+1);
                mongoTemplate.updateFirst(Query.query(Criteria.where("_id").is(planetId)),Update.update("group",group), Planet.class);
            }
            return R.ok();
        }
    }
    

    Planet 是帖子对象,Group 是组队属性,member 是存储具体组队对象的属性,注释的三行代码是创建 User 对象用于测试的代码,可以无视,synchronized 代码块上做的事就是取得帖子对象并做判断,代码块中往帖子对象中的 Group 属性赋予对应值最后更新数据库,也就是正式的加入组队方法。 就这些了,在线等 dalao 救一救

    8 条回复    2023-01-18 10:56:29 +08:00
    yeqizhang
        1
    yeqizhang  
       2023-01-06 22:47:20 +08:00 via Android
    可能是让你在 synchronized 中再查一次判断一次组队人数满没满?就是只有锁住的代码块会改变数量,在当前线程拿到锁时,上一个拿到锁的线程已经将组队人数搞满的。你可以多同时请求一下加入组队,会看到数据库中组队字段的人数超出设置的限制了。
    TWorldIsNButThis
        2
    TWorldIsNButThis  
       2023-01-06 23:07:57 +08:00
    没用过 mongodb
    mongodb 没有行锁之类的吗
    为啥要代码锁 锁 this 不就是全局都限制了,然而 planetId 不同的时候并不需要锁
    TWorldIsNButThis
        3
    TWorldIsNButThis  
       2023-01-06 23:19:05 +08:00
    双重锁的话,本质是拿到锁后要操作的东西不一定是最新状态,所以要再临界区里再读取一次,获取操作对象最新的状态并再次校验

    你这个例子里就是两个人同时拿到一个 group ,
    a 上锁,加入,保存,解锁
    然后 b 再上锁,但是 b 操作的 group 对象还是 a 保存前的那个 group ,并不知道 a 已经加入并保存了,
    所以要再读取一次数据库拿到最新的 group 再进行操作
    GTim
        4
    GTim  
       2023-01-07 09:21:22 +08:00
    用 redis incr... +1 大于总人数,那么就放弃。
    GTim
        5
    GTim  
       2023-01-07 09:21:37 +08:00
    @GTim 不然就用楼上的
    Alan0000
        6
    Alan0000  
       2023-01-07 11:01:59 +08:00
    多个线程操作数据库的时候,组队人数可能会多出,应该是在 sync 里面再写个"组队人数已满"的判断吧
    tiRolin
        7
    tiRolin  
    OP
       2023-01-16 16:36:34 +08:00
    @TWorldIsNButThis 谢谢你,你的回答完美解决了我的问题,当时做完了不知道行不行,现在确定没问题了,我才回复你的,所以有些晚
    z9ln
        8
    z9ln  
       2023-01-18 10:56:29 +08:00
    我理解的双重检验是在正逻辑里做容错,你用反逻辑直接返回的思路和这个模式是反的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2573 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 10:30 · PVG 18:30 · LAX 02:30 · JFK 05:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.