本文最后更新于 2025-01-13,转载请标明原作者!

synchronized锁的是对象,而非值,所以synchronized是不能直接锁字符串的,例如:

public void test(String qq){
  synchronized (qq){
      ...
  }
}

这段代码本意是想锁住同一QQ号码的代码块,但是每次传过来的字符串都是不同的字符串对象,synchronized 就失去了意义。

而如果想要锁住同一QQ,就需要使每次传过来的QQ是同一字符串对象。

方案一

将传过来的QQ放入常量池。

public void test(String qq){
  synchronized (qq.intern()){
      ...
  }
}

nameObj.intern()把字符串对象放入常量池中。通过将传过来的QQ放入常量池中,这样多线程并发调用传入相同的QQ时,实际使用同一个常量池中的对象作为锁对象,保证了同步的线程安全性。


synchronized 锁字符串用intern()存在的问题

常量池大小依赖于服务器内存,且回收垃圾只依赖于fullGC,如果数据过大,则很容易造成服务器频繁fullGC,所以不建议直接使用intern()。


方案二

使用google的guava包的interner类

需要导入google的guava包:

Maven:

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>33.3.1-jre</version>
  <!-- or, for Android: -->
  <version>33.3.1-android</version>
</dependency>

Gradle:

dependencies {
  // Pick one:

  // 1. Use Guava in your implementation only:
  implementation("com.google.guava:guava:33.3.1-jre")

  // 2. Use Guava types in your public API:
  api("com.google.guava:guava:33.3.1-jre")

  // 3. Android - Use Guava in your implementation only:
  implementation("com.google.guava:guava:33.3.1-android")

  // 4. Android - Use Guava types in your public API:
  api("com.google.guava:guava:33.3.1-android")
}

示例代码:

public class Test(){
  private static Interner<String> lock = Interners.newWeakInterner();
  public void test(String qq){
    synchronized (lock.intern(qq)){
        ...
    }
  }
}

Interner是通过MapMaker构造ConcurrentMap来实现弱引用,ConcurrentMap用分段的方式保证安全。比常量池的优点就在于这里是弱引用的方式,便于map的回收,常量池只能依赖于fullGC,这里的回收在不使用或内存不够用条件下即可被回收(Minor GC阶段)。