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

相关推荐
二十七剑36 分钟前
jvm中各个参数的理解
java·jvm
东阳马生架构2 小时前
JUC并发—9.并发安全集合四
java·juc并发·并发安全的集合
计算机小白一个2 小时前
蓝桥杯 Java B 组之岛屿数量、二叉树路径和(区分DFS与回溯)
java·数据结构·算法·蓝桥杯
孤雪心殇2 小时前
简单易懂,解析Go语言中的Map
开发语言·数据结构·后端·golang·go
菠菠萝宝3 小时前
【Java八股文】10-数据结构与算法面试篇
java·开发语言·面试·红黑树·跳表·排序·lru
不会Hello World的小苗3 小时前
Java——链表(LinkedList)
java·开发语言·链表
Allen Bright3 小时前
【Java基础-46.3】Java泛型通配符详解:解锁类型安全的灵活编程
java·开发语言
柃歌3 小时前
【UCB CS 61B SP24】Lecture 7 - Lists 4: Arrays and Lists学习笔记
java·数据结构·笔记·学习·算法
柃歌3 小时前
【UCB CS 61B SP24】Lecture 4 - Lists 2: SLLists学习笔记
java·数据结构·笔记·学习·算法
是姜姜啊!4 小时前
redis的应用,缓存,分布式锁
java·redis·spring