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

相关推荐
郑重其事,鹏程万里4 分钟前
表达式计算器(mvel2)
java
其实防守也摸鱼7 分钟前
软件安全与漏洞--软件安全编码
java·前端·网络·安全·网络安全·web·工具
888CC++17 分钟前
栈上分配 VS 堆分配 核心区别
java·开发语言·jvm
艾利克斯冰18 分钟前
Java面试题汇总
java
我是一颗柠檬1 小时前
【JavaSE全面教学】Java集合框架下Day13(2026年)
java·开发语言·intellij-idea
vx-程序开发1 小时前
基于机器学习的动漫可视化系统的设计与实现-计算机毕业设计源码08339
java·c++·spring boot·python·spring·django·php
计算机魔术师1 小时前
【AI面试八股文 Vol.3.4:训练微调部署选型】从预训练到量化部署:LLM 工程落地如何做模型选择
人工智能·后端·面试·架构·moe·vol.3.3·vol.3.4
明月_清风1 小时前
从零到一构建生产级 AI Agent:架构拆解 × Python 高并发实战 × 技术选型方法论
后端·agent
LCG元1 小时前
RAG工程指南:从基础检索到生产部署全解析
java·运维·数据库
石榴树下的七彩鱼2 小时前
医疗票据 OCR 识别 API 多场景落地指南:医保结算 + 商保理赔 + 医疗信息化(附 Python/Java 完整示例)
java·python·ocr·石榴智能·医疗票据ocr·医保结算·ocrapi