当String是锁的对象时,String.intern()和Striped哪个更好?

When String is the object of the lock, which is better String.intern() or Striped?

想象一个场景,我们需要根据设备id来加锁,设备id是一个字符串。很多人推荐使用String.intern()作为锁对象,但是也有人推荐使用Striped进行并发控制,比如下面的代码:

import com.google.common.util.concurrent.Striped;

import java.util.concurrent.locks.Lock;

public class Test {

    private Striped<Lock> striped = Striped.lock(8);

    public void businessLogicByStringIntern(String deviceId) {
        // use deviceId.intern() as lock
        synchronized (deviceId.intern()) {
            // execute code thread-safely
        }
    }

    public void businessLogicByStriped(String deviceId) {
        // use striped lock
        synchronized (striped.get(deviceId)) {
            // execute code thread-safely
        }
    }

}

更推荐哪个实现,businessLogicByStringInternbusinessLogicByStriped

参考:
Striped (Guava: Google Core Libraries for Java 19.0 API)

在第一个版本中,每个设备都有一个唯一的锁,由 deviceId 字符串区分。在这种情况下 intern 是必需的。

在第二个版本中,您只有(比方说)8 把锁,并且它们在所有设备上都是条带化的。


有什么区别?

  • 第一个版本创建了更多的原始锁。但是原始锁很便宜,除非存在锁争用。使用此版本,如果两个或多个线程确实试图对同一设备执行某些操作,则只会发生锁争用。

  • 第二个版本不会创建超过 8 个锁,它们是 Lock 个对象。 Lock API 提供了比原始锁更多的功能......如果这对你有用的话。

    但是对于这个版本,你会得到更多的争论。例如,如果两个不同的设备使用相同的条带锁,那么这两个设备就会相互排斥......这可能是您不想要的。

使用内部字符串作为锁标识符也存在理论上的问题。 interned 字符串的 space 是 JVM 范围的,因此如果您的应用程序的不同部分 独立地 以这种方式锁定事物(例如使用 interned 设备 id 字符串),它们可能干扰。应用程序的两个部分可能会意外地共享锁。 (这个问题在 中有更深入的讨论)

如果这是一个真正的问题,有一些方法可以避免这种情况。例如,应用程序的两个部分可以在实习之前向字符串添加(不同的)前缀等。


哪个更好?

好吧,这取决于 1) 您要优化的内容以及 2) 锁可能持有多长时间;即由条带化引起的不必要争用的成本

在大多数情况下,第一个版本(每个设备一个不同的锁)会更好。但是,如果您有大量设备,那么大量驻留字符串的成本可能会很高。在那种情况下,如果锁持有时间很短,那么锁条带化可能会更好。