在讨论 Java 同步问题时,有人评论说以下代码片段不等效(并且可能编译为不同的字节码):
public synchronized void someMethod() {
//stuff
}
和
public void someMethod() {
synchronized (this) {
//stuff
}
}
它们是等价的吗?
在讨论 Java 同步问题时,有人评论说以下代码片段不等效(并且可能编译为不同的字节码):
public synchronized void someMethod() {
//stuff
}
和
public void someMethod() {
synchronized (this) {
//stuff
}
}
它们是等价的吗?
它们在功能上是等效的,尽管我测试的编译器(Java 1.6.0_07 和 Eclipse 3.4)生成不同的字节码。第一个生成:
// access flags 33
public synchronized someMethod()V
RETURN
第二个生成:
// access flags 1
public someMethod()V
ALOAD 0
DUP
MONITORENTER
MONITOREXIT
RETURN
(感谢ASM的字节码打印)。
因此它们之间的差异持续到字节码级别,并且由 JVM 来使它们的行为相同。但是,它们确实具有相同的功能效果 - 请参阅 Java 语言规范中的示例。
应该注意的是,如果该方法在子类中被覆盖,则它不一定是同步的——因此在这方面也没有区别。
我还运行了一个测试来阻止一个线程尝试在每种情况下访问监视器,以比较它们的堆栈跟踪在线程转储中的样子,并且它们都包含有问题的方法,所以那里也没有区别。
我最初的评论是这些陈述是相同的。
在这两种情况下,首先发生的事情是调用线程将尝试获取当前对象的(意思是this
')监视器。
我不知道不同的字节码,我很高兴听到不同之处。但实际上,它们是 100% 相同的。
编辑:我要澄清这一点,因为这里有些人弄错了。考虑:
public class A {
public synchronized void doStuff()
{
// do stuff
}
}
public class B extends A {
public void doStuff()
{
// do stuff
// THIS IS OVERRIDE!
}
}
在这种情况下doStuff()
,B 类仍然会覆盖doStuff()
A 类,即使它没有同步。
同步关键字绝不是合同的一部分!不适用于子类,不适用于接口,不适用于抽象类。
我发表了原始评论。我的评论是它们在逻辑上是等价的,但是编译成不同的字节码。
当时我没有添加任何其他内容来证明它的合理性,因为实际上没有什么可以证明的——它们只是编译成不同的字节码。如果您将方法声明为synchronized,则该同步是方法定义的一部分。方法中的同步块不是方法定义的一部分,而是涉及单独的字节码来获取和释放监视器,如上面的一张海报所示。严格来说,它们是略有不同的东西,尽管对于你程序的整体逻辑来说,它们是等价的。
这什么时候有关系?好吧,在大多数现代桌面虚拟机上,几乎没有。但例如:
是的。在实例方法上使用同步关键字使用“this”作为监视器,在类方法(静态方法)上使用它也使用类的类对象(Foo.class)。
这样,您可以同步整个方法,同时使用同步块样式将其与另一个方法中的代码片段同步。
我看不出任何功能差异 - 两者都在 (this) 上同步它们的整个方法体。评论这些不同的人如何证明他们的说法是正确的?
它们在功能上并不完全相同。其他代码可以使用反射来查看您的方法是否具有同步修饰符,但是如果不读取其字节码,则无法判断方法是否包含同步块。
确定方法是否同步的能力偶尔会派上用场。就个人而言,在面向方面的编程中进行同步时,我使用该标志来避免冗余锁定。
MyObject myObjectA;
MyObject myObjectB;
public void someMethod() {
synchronized (this) {
//stuff
}
}
public void someMethodA() {
synchronized (myObjectA) {
//stuff
}
}
public void someMethodB() {
synchronized (myObjectB) {
//stuff
}
}
在这种情况下:
someMethod
阻止整个班级someMethodA
myObjectA
仅块someMethodB
myObjectB
仅块someMethodA
并且someMethodB
可以同时调用