synchronized还能用形参作为锁的对象?

最近整理项目的时候,发现一段代码很有意思,正好拿来研究一下synchronized对象的选择。 众所周知,synchronized是使用在方法或者代码块上,锁的对象分为类锁和对象锁。我们一般常用的都是代码块,获取的是对象锁。项目中的代码也是如此,但是选择的对象有点神奇。模拟线上的示例代码如下:

java 复制代码
    public Member getWxUserPhoneId(String username, Integer lock) {
        Member member;
            synchronized (username){
                 member = memberService.findMember(username, null);
                if (member != null) {
                    return member;
                }
                // 打印线程名称
                System.out.println(Thread.currentThread().getName());
                member = new Member();
                member.setUsername(username);
                member.setPassword("password");
                try {
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                memberService.save(member);
            }

        return member;
    }
java 复制代码
    @GetMapping("/findMember")
    public ResultMessage<Object> findMember(@NotNull(message = "用户名不能为空") @RequestParam String username){
        //String s= new String(username);
        return ResultUtil.data(memberService.getWxUserPhoneId(username));
    }

想必大家也都看出来了,他选择用形参作为锁的对象,是不能保证每个线程获取的对象锁是相同的,事实也是如此。模拟四个线程同时请求

一般来说,我们使用对象锁的时候,会用静态类作为锁的对象,这样才能保证多线程下获取的对象锁是同一个。但是用形参是否也可以保证对象唯一呢,当然总有旁门左道去实现。java形参的传递方式分为值传递和引用传递,除了基本类型,都是将对象的引用地址作为副本。而synchronized判断锁对象是否相同,简单理解也是判断对象的地址。所以只要保证传入的对象是地址是唯一的,就可以保证线程安全。将代码的锁对象修改成lock

java 复制代码
    public Member getWxUserPhoneId(String username, Integer lock) {
            Member member;
            synchronized (lock){
                 member = memberService.findMember(username, null);
                if (member != null) {
                    return member;
                }
                //打印线程名称
                System.out.println(Thread.currentThread().getName());
                member = new Member();
                member.setUsername(username);
                member.setPassword("password");
                try {
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                memberService.save(member);
            }

        return member;
    }
java 复制代码
    @GetMapping("/findMember")
    public ResultMessage<Object> findMember(@NotNull(message = "用户名不能为空") @RequestParam String username){
        //String s= new String(username);
        return ResultUtil.data(memberService.getWxUserPhoneId(username,1));
    }

重新执行一遍,成功了,不存在的情况下只有一个线程进入到代码中(Integer有缓存,所以1的内存地址都是相同的)

这里就有点奇怪了,按道理String也有缓冲区进行缓存,为什么还是会发生这个问题,具体可能跟springboot有关系,测试下来会发现传入的username的内存地址都不相同,可能是因为它每次都会构建新的String对象导致的,将代码修改一下

java 复制代码
    @GetMapping("/findMember")
    public ResultMessage<Object> findMember(@NotNull(message = "用户名不能为空") @RequestParam String username){
        //String s= new String(username);
        //获取缓冲池中的字符串
        return ResultUtil.data(memberService.getWxUserPhoneId(username.intern(),null));
    }

bingo,只有一条线程进入

总结来说,synchronized使用对象锁是,只要对象内存地址一致,无论是形参还是静态对象都可以保证,线程同步,但是还是不建议使用形参作为锁对象,本质跟静态对象是相同的,但是用形参会多出额外的理解成本和不可控的风险(例如锁影响的范围)。

相关推荐
金銀銅鐵9 分钟前
[Java] 用 GUI 展示 ArrayList 扩容的过程
java·后端·ai编程
磊 子11 分钟前
什么是虚假唤醒(spurious wakeup)?从 POSIX 标准讲到 futex 内核
java·开发语言
飘然生22 分钟前
traceId链路 skywalking java agent
java·开发语言·skywalking
IT研究室28 分钟前
最新计算机毕业设计选题推荐-基于spring boot的母婴全程服务管理系统-网站-文档指导-Java-springboot
java·spring boot·课程设计
CRMEB37 分钟前
商品 SKU 设计,从简单规格到多规格踩过什么
java·前端·数据库·开源
霸道流氓气质38 分钟前
基于 Spring @Profile 的 MQ 消费者按需开关设计
java·后端·spring
silvamux_1 小时前
4款新模型商务邮件翻译横评:0.98到0.46
android·java·javascript
Dovis(誓平步青云)1 小时前
车辆进入隧道后信号断开,座舱旅程怎样补回事件
java·开发语言·前端·数据库·电脑
砚底藏山河1 小时前
数据新鲜度 SLA 监控
java·大数据·python·maven