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

相关推荐
Jooolin几秒前
Flask 入门到实战(2):使用 SQLAlchemy 打造可持久化的数据层
后端·flask·ai编程
代码小将9 分钟前
java中static学习笔记
java·笔记·学习
std787912 分钟前
VITA STANDARDS LIST,VITA 最新标准清单大全下载_ansi vita 2025
java·前端·javascript
迢迢星万里灬27 分钟前
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
java·spring boot·spring·mybatis·计算机基础·面试指南
小远同学30 分钟前
java Mavlink连接模拟器 开源软件Mission Planner简单使用(一)
后端
Jooolin30 分钟前
Flask 入门到实战:手把手带你构建第一个 Python Web 应用
后端·flask·ai编程
烟沙九洲30 分钟前
@Transactional 什么情况下会失效
java·spring
会飞的哈士奇42 分钟前
Html实现图片上传/裁剪/马赛克/压缩/旋转/缩放
java·spring·html
红鼻子时代43 分钟前
Django RBAC项目后端实战 - 03 DRF权限控制实现
后端·python·django·rabc
语落心生1 小时前
Mcp+Agent - 自动化BI报表实现方案探索
后端