Java: 多线程同步中偶尔多个线程同入synchronized块?

源码如下:

public class Main {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        HashSet<String> s = new HashSet<>();
        String id = "x12323";
        for(int j = 0; j<20; j++) {
            fixedThreadPool.execute(() -> {
                System.out.println("运行线程: " + Thread.currentThread().getName());
                String lockObj = s.getClass().toString() + id; 
                synchronized (lockObj) {
                    if (s.isEmpty()) {
                        System.out.println(Thread.currentThread().getName() + " : lockObj(" + lockObj+ ") hash code ("
                            + lockObj.hashCode() + "),集合 s 为空吗:" + s.isEmpty());
                    s.add("good");
                    }
                }
            });
        }
    }
}

运行结果:


    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-2
    pool-1-thread-1 : lockObj(class java.util.HashSetx12323) hash code (-1786802297),集合 s 为空吗 :true
    pool-1-thread-2 : lockObj(class java.util.HashSetx12323) hash code (-1786802297),集合 s 为空吗:true
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-1
    运行线程: pool-1-thread-5
    运行线程: pool-1-thread-4
    运行线程: pool-1-thread-3

为何这两个线程都进入互斥块了?

    pool-1-thread-1 : lockObj(class java.util.HashSetx12323) hash code (-1786802297),集合 s 为空吗 :true
    pool-1-thread-2 : lockObj(class java.util.HashSetx12323) hash code (-1786802297),集合 s 为空吗:true
阅读 2.5k
1 个回答

更新 编译后常量池和字节码

默认编译 Java 8 查看编译后的 class 文件

constant_pool_count 为 146,
其中只有

#005 (String): x12323
#013 (String): 运行线程:
#020 (String): :lockObj(
#021 (String): hash code (
#024 (String): ),集合 s 为空吗:
#026 (String): good
#030 - #052 (UTF-8)
#056 - #058 (UTF-8)
#060 - #061 (UTF-8)
#065 - #066 (UTF-8)
#070 (UTF-8): BootstrapMethods
#078 - #079 (UTF-8)
#087 - #088 (UTF-8)
#091 (String): ),集合 s 为空吗:
#093 (String): good
#095 - #107 (UTF-8)
#110 - #133 (UTF-8)
#137 - #138 (UTF-8)
#140 - #142 (UTF-8)
#144 - #0145 (UTF-8)

再看字节码部分:

clipboard.png

037: aload_0 是从局部变量表里加载0号值
041: ldc #5->x12323 是加载常量池6号值

所以说 classname 不是常量值入栈,个人猜测是因为

有泛型之后,Java 又选择了泛型擦除,很多东西要等到运行时才能确定,getClass 这个操作应该就是带有不确定性,所以它的结果不进入常量池。

更新 显示更多的调试信息

右击变量区,点击 Customize Data Views...

clipboard.png

选你要的,我是全部勾选了。

clipboard.png

原答案

hasCode 一样并不说明两个 String 实例是同一个,String.hashCode只是计算value值,同样内容的情况下 hashCode 会一样的。

String lockObj = s.getClass().toString() + id; 后边加一句打印语句并下断点。

clipboard.png

第一次循环时创建的 lockObj 的内存数据是这样的

clipboard.png

这是第二次的内存数据。

可以看到,这里的两个 String 的值即使一样,它们的 value 属性却不是同一个指向。

所以你锁的对象其实本就不是同一个,把 lockObj 的声明放到循环外部后你锁的才是同一个对象。

个人猜测:

你的 lockObj 是一个依赖运行时变量hashCode而拼接的字符串,而且不是常量字符串的拼接,也就是说编译器无法在编译期优化掉它,也就不会在字符串池里放这个。

也就是说,每次循环时 JVM 会重新创建一个 byte[],这就导致了每次 lockObj 实际上不是同一个实例。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题