Java String不可变性与对象实例:字符串同步锁问题探讨
关于Java中String对象同步的锁范围问题
这个问题问到点子上了——结合String的常量池特性和不可变性,同步String确实容易踩坑,咱们一步步说清楚:
首先得明确两个核心前提:
- 字符串字面量(比如
"hello")会被JVM存入字符串常量池,相同内容的字面量会指向同一个对象实例; new String("hello")会创建一个新的String对象,除非你调用intern()方法,才会让它指向常量池里的对应实例。
回到你的问题:是否会锁定所有相同内容的String实例?答案分两种情况:
1. 当同步的是字符串字面量或intern()后的String对象时——会锁定所有指向该实例的引用
因为这些String引用本质上指向的是常量池里的同一个对象,而synchronized关键字锁的是对象实例本身,不是字符串内容。举个代码例子:
// s1和s2指向常量池里同一个"test"对象 String s1 = "test"; String s2 = "test"; // 这里锁的是常量池中的"test"实例 synchronized(s1) { System.out.println("持有s1锁"); try { Thread.sleep(3000); } catch (Exception e) {} } // 执行到这里时会被阻塞,因为s2和s1是同一个对象 synchronized(s2) { System.out.println("持有s2锁"); }
这种情况下,任何其他地方用相同字面量或intern()后的"test"做锁,都会被这个同步块阻塞,哪怕是完全无关的代码模块。
2. 当同步的是new String()创建的对象时——不会锁定其他相同内容的String实例
用new String("test")创建的对象是独立的,哪怕内容和常量池里的一样,也是不同的实例。同步这种对象时,只会锁定当前实例,不会影响其他内容相同但实例不同的String:
String s1 = new String("test"); String s2 = new String("test"); // 锁的是s1这个独立实例 synchronized(s1) { System.out.println("持有s1锁"); try { Thread.sleep(3000); } catch (Exception e) {} } // 这里不会被阻塞,因为s2是另一个完全独立的对象 synchronized(s2) { System.out.println("持有s2锁"); }
重要提醒:尽量不要用String作为锁对象
用字符串字面量做锁有很大风险:
- 不同的业务模块如果碰巧用了相同内容的字符串做锁,会意外触发跨模块的锁竞争,导致性能问题甚至死锁;
- 恶意代码可以通过
"test".intern()拿到常量池里的同一个对象,故意加锁阻塞你的正常逻辑。
更安全的做法是使用专门的私有锁对象,比如:
private final Object lock = new Object(); // 用这个私有对象做锁 synchronized(lock) { // 业务逻辑 }
内容的提问来源于stack exchange,提问作者klonq
相关产品推荐
相关产品推荐

