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

相关推荐
fatfishccc31 分钟前
Spring MVC 全解析:从核心原理到 SSM 整合实战 (附完整源码)
java·spring·ajax·mvc·ssm·过滤器·拦截器interceptor
没有bug.的程序员39 分钟前
MyBatis 初识:框架定位与核心原理——SQL 自由掌控的艺术
java·数据库·sql·mybatis
执键行天涯1 小时前
从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑
java·前端·github
程序员江鸟1 小时前
Java面试实战系列【JVM篇】- JVM内存结构与运行时数据区详解(私有区域)
java·jvm·面试
回家路上绕了弯1 小时前
ClickHouse 深度解析:从核心特性到实战应用,解锁 OLAP 领域新势能
数据库·后端
架构师沉默1 小时前
Java 状态机设计:替代 if-else 的优雅架构
java·程序员·架构
java亮小白19971 小时前
Spring Cloud 快速通关之Sentinel
java·spring cloud·sentinel
atwednesday1 小时前
大规模文档预览的架构设计与实现策略
java
xiaok2 小时前
本地用VScode的Live Server监听5500访问页面,ubuntu上不需要在配置5500
后端
雨绸缪2 小时前
ABAP 时间戳
后端