题
在讨论 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决定它们的行为是否相同。但是,它们具有相同的功能效果 - 请参阅示例。
应该注意的是,如果在子类中重写该方法,则它不一定是同步的 - 因此在这方面也没有区别。
我还运行了一个测试来阻止尝试访问监视器的线程在每种情况下比较它们的堆栈跟踪在线程转储中的样子,并且它们都包含有问题的方法,因此也没有区别。
其他提示
我做了原始评论,声明是相同的。
在这两种情况下,首先发生的事情是调用线程将尝试获取当前对象(意思是 this
')监视器。
我不知道不同的字节码,我会很高兴听到差异。但在实践中,它们是完全相同的。
编辑:我要澄清这一点,因为有些人在这里弄错了。考虑:
public class A {
public synchronized void doStuff()
{
// do stuff
}
}
public class B extends A {
public void doStuff()
{
// do stuff
// THIS IS OVERRIDE!
}
}
在这种情况下,B类中的 doStuff()
仍然会覆盖A类中的 doStuff()
,即使它未同步。
同步关键字永远不是合同的一部分!不适用于子类,不适用于接口,不适用于抽象类。
我发表了最初的评论。我的评论是它们在逻辑上是等价的,但是 编译为不同的字节码.
当时我没有添加任何其他内容来证明它的合理性,因为实际上没有太多可以证明的——他们只是 做 编译为不同的字节码。如果您将方法声明为 同步的, ,那么同步就是方法定义的一部分。 方法内的同步块 不是 方法定义的一部分, ,而是涉及单独的字节码来获取和释放监视器,如上面的海报之一所示。严格来说,它们略有不同 到 程序的整体逻辑, ,它们是等价的.
这什么时候重要?嗯,在大多数现代桌面虚拟机上,几乎没有。但例如:
- 原则上虚拟机可以在一种情况下进行优化,但不能在另一种情况下进行优化
- 有一些 JIT 编译器优化,其中 方法中的字节码数量被视为进行优化的标准
- 虚拟机 没有 JIT 编译器(诚然现在很少,但也许在较旧的移动设备上?)将在每次调用时处理更多字节码
是。在实例方法上使用synchronized关键字使用'this'作为监视器,同时在类方法上使用它(静态方法)使用类'Class object(Foo.class)。
通过这种方式,您可以同步整个方法,同时使用synchronized-block样式将其与另一个方法中的代码段同步。
我看不到任何功能差异 - 两者同步它们的整个方法体(this)。评论这些不同的人如何证明他们的陈述是正确的?
它们的功能并不完全相同。其他代码可以使用反射来查看您的方法是否具有synchronized修饰符,但是无法判断方法是否包含同步块而不读取其字节码。
确定方法是否偶尔同步的能力派上用场。就个人而言,在面向方面编程中进行同步时,我已经使用该标志来避免冗余锁定。
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
可以同时调用