使用synchronized锁String的问题

使用场景

在导出文件时候,我们规定同一用户只能同时导出一份数据。此时我们选择使用"USER"+userName来作为锁。
代码如下:

        String key = "USER" + user.getName();
        synchronized (key) {
            //todo
        }

其中"USER" + user.getName()会优化成StringBuilder,然后调用StringBuildertoString方法,我们可以看到,这个时候实际上创建了一个新的String对象
在这里插入图片描述
因此,即使是相同的用户,产生了相同字符串的key,也不是同一个锁。
测试代码如下:

        PoJo poJo1 = PoJo.of("asd", BigDecimal.ZERO);
        PoJo poJo2 = PoJo.of("asd", BigDecimal.ONE);
        final String key1 = poJo1.getName() + "123";
        final String key2 = poJo2.getName() + "123";
        new Thread(() -> {
            synchronized (key1) {
                System.out.println("k1 start");
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("k1 end");
            }
        }).start();
        new Thread(() -> {
            synchronized (key2) {
                System.out.println("k2 start");
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("k2 end");
            }
        }).start();

输出如下
在这里插入图片描述
可以看到不是我们预想的k1k2串行执行的结果。

解决

为什么选择用String作为key来同步代码?是因为考虑到字符串常量池的存在,相同的字符串在常量池中只保存一份。
但是由于被编译器优化成StringBuilder,所以不符合原本的设想。
故,使用intern()方法即可获取字符串在常量池中对象。
在这里插入图片描述
执行结果如下
在这里插入图片描述
符合预期。

缺点

intern()方法是返回常量池中的对象,因此存在多步判断,会影响性能。

        long s1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            synchronized (String.valueOf(i)) {
                int i1 = 3 / 1;
            }
        }
        System.out.println(System.currentTimeMillis() - s1);
        s1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            synchronized (String.valueOf(i).intern()) {
                int i1 = 3 / 1;
            }
        }
        System.out.println(System.currentTimeMillis() - s1);

百万次循环时候,上下两者时间差距为
在这里插入图片描述
千万次循环时候,上下两者时间差距为
在这里插入图片描述
但实际上可以忽略不计,业务中并没有千万级的QPS

String的编译器优化

public class TestString {
    static class Pojo {
        String name;

        public Pojo(String name) {
            this.name = name;
        }
    }
    public static void main(String[] args) {
        Pojo pojo1 = new Pojo("asd");
        Pojo pojo3 = new Pojo("asd123");
        String str1 = "asd" + "123";
        String str2 = pojo1.name + "123";
        String str3 = pojo3.name;
    }
}

代码如上,我们使用javap -c TestString反编译后,可以看到

str1

在这里插入图片描述
直接优化成String str1 = "asd123"

str2

在这里插入图片描述
使用了StringBuilderappendtoString来优化。

str3

在这里插入图片描述
直接引用Pojo类的field

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值