Java同步锁如何比较锁定对象?

7
我希望在多个API请求中锁定一个对象,以便每个用户只能进入一段代码块中的一个请求。 synchronized(obj)是基于对象引用还是其hashCode()函数进行锁定?
也就是说,我可以这样做:
synchronized("asdf") {
    doSomethingNifty();
}

在这里,“asdf”具有唯一的哈希值,但没有唯一的引用。

它在对象上进行同步。 - Dave Newton
"asdf" 实际上具有唯一的引用:它是一个字符串字面量,而所有的字符串字面量都引用同一个 String 实例。 - parsifal
3个回答

12
“请问 synchronized(obj) 是基于对象的内存位置还是它的 toHash() 函数进行锁定?” “都不是。它是在与对象相关的监视器上进行锁定。就 JVM 而言,我们不谈论对象的内存地址,因为它是可重定位的,并且它并不是散列码(即使在 Object.hashcode() 方面也是如此),因为它不是唯一的。” “就应该锁定什么而言,应该是相同的 final 对象。例如:”
private final Object lockObject = new Object();
...
synchronized (lockObject) {
   // do stuff that needed to be protected
}

您希望将其设置为“final”,以便可以保证多个线程锁定的是相同的不变对象引用。使用“private”很好,因为外部类无法破坏类内部的锁定。

这里的“asdf”具有唯一的哈希值,但没有唯一的内存地址。

"asdf"并没有唯一的哈希值,因为其他字符串可能具有相同的哈希值;如果编译器将其存储在Java字符串池中,则它实际上可能在应用程序中的所有使用中具有唯一的“内存地址”。这意味着某些完全不同的类也可能具有相同的错误模式代码块,并且会影响您的类的同步,因为它将锁定相同的String对象实例。这就是为什么私有锁定对象如此重要的原因。

顺便说一下,您还必须永远不要对可变值(例如非最终对象,如BooleanInteger)进行同步。以下模式经常被使用,但是非常错误:

Boolean value = false;
...
// really bad idea
synchronized (value) {
   if (value) {
      value = false;
   } else {
      value = true;
   }
}

这是非常错误的,因为"value"引用正在改变。因此,一个线程可能会锁定它,然后更改它的引用值,以便另一个线程将锁定另一个对象,并且两个线程将同时处于"synchronized"中。更糟糕的是,由于"Boolean"只有两个值"true"和"false",它们是常量,所以多个类将锁定相同的引用。

2

您正在对一个对象进行同步,内存地址问题纯粹是实现问题,与您无关。只要它是相同的对象(意味着完全相同的实例),同步就会完成。

如果使用不同的实例,则同步将无法工作。您可以定义一个公共静态常量作为锁:

public final static Object LOCK = new Object();

并使用它

synchronized(SomeClass.LOCK) {

为什么要使用String而不是Object - Piotr Praszmo
1
在字符串字面值上同步是不好的实践,可能会导致错误。字符串字面值“asdf”出现在字节码中作为一个字符串常量,并且在类加载时被“interned”。如果两个类都使用相同的字符串这样做,那么两个字段都引用相同的interned字符串。这意味着您有一个单独的锁对象,而您认为有两个。因此,您可能会看到不正确的行为,甚至死锁。 - Martin Ellis

0

锁定是在对象实例本身上进行的,如果您想这样考虑的话,可以将其视为内存位置。因此,您提出的建议行不通。

相反,听起来您只需要一个与要保护的代码块对应的单个对象。因此,在程序的设置过程中,您需要类似于以下内容:

static Object lockObject = new Object();

然后你就可以做

synchronized(lockObject) {
    doSomethingNifty();
}

然而,如果doSomethingNifty()与特定对象相关,则使用该对象而不是lockObject将非常有意义。此外,请确保doSomethingNifty()执行速度快,否则会有很多用户等待。


lockObject 应该比 static 更多地使用 final - Gray
@Gray 好的,当然可以 :-),但我通常发现需要为lockObject所在类的所有实例只有一个lockObject。如果是这样,那么static确实是必需的。我同意final很好,非常准确等等,但它只是保护lockObject不被意外更改,如果它是一个单一用途对象仅用于同步目的,那么这种编程错误是不太可能发生的。 - Stochastically
明白了。另外,final 在多线程程序中还有许多关于对象初始化的好处。 - Gray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接