8

在讨论 Java 同步问题时,有人评论说以下代码片段不等效(并且可能编译为不同的字节码):

public synchronized void someMethod() {
  //stuff
}

public void someMethod() {
  synchronized (this) {
    //stuff
  }
}

它们是等价的吗?

4

7 回答 7

13

它们在功能上是等效的,尽管我测试的编译器(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 语言规范中的示例

应该注意的是,如果该方法在子类中被覆盖,则它不一定是同步的——因此在这方面也没有区别。

我还运行了一个测试来阻止一个线程尝试在每种情况下访问监视器,以比较它们的堆栈跟踪在线程转储中的样子,并且它们都包含有问题的方法,所以那里也没有区别。

于 2009-01-06T17:14:46.310 回答
7

我最初的评论是这些陈述是相同的。

在这两种情况下,首先发生的事情是调用线程将尝试获取当前对象的(意思是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 类,即使它没有同步。

同步关键字绝不是合同的一部分!不适用于子类,不适用于接口,不适用于抽象类。

于 2009-01-06T16:58:59.760 回答
4

我发表了原始评论。我的评论是它们在逻辑上是等价的,但是编译成不同的字节码

当时我没有添加任何其他内容来证明它的合理性,因为实际上没有什么可以证明的——它们只是编译成不同的字节码。如果您将方法声明为synchronized,则该同步是方法定义的一部分。方法中的同步块不是方法定义的一部分,而是涉及单独的字节码来获取和释放监视器,如上面的一张海报所示。严格来说,它们是略有不同的东西,尽管对于你程序的整体逻辑来说,它们是等价的

这什么时候有关系?好吧,在大多数现代桌面虚拟机上,几乎没有。但例如:

  • VM 原则上可以在一种情况下进行优化,但不能在另一种情况下进行优化
  • 有一些 JIT 编译器优化,其中将方法中的字节码数量作为优化的标准
  • 没有JIT 编译器的虚拟机(现在确实很少,但可能在较旧的移动设备上?)每次调用都会有更多的字节码要处理
于 2009-01-06T18:09:27.793 回答
2

是的。在实例方法上使用同步关键字使用“this”作为监视器,在类方法(静态方法)上使用它也使用类的类对象(Foo.class)。

这样,您可以同步整个方法,同时使用同步块样式将其与另一个方法中的代码片段同步。

于 2009-01-06T16:58:27.030 回答
0

我看不出任何功能差异 - 两者都在 (this) 上同步它们的整个方法体。评论这些不同的人如何证明他们的说法是正确的?

于 2009-01-06T16:58:06.703 回答
0

它们在功能上并不完全相同。其他代码可以使用反射来查看您的方法是否具有同步修饰符,但是如果不读取其字节码,则无法判断方法是否包含同步块。

确定方法是否同步的能力偶尔会派上用场。就个人而言,在面向方面的编程中进行同步时,我使用该标志来避免冗余锁定。

于 2012-04-20T20:14:47.003 回答
-1
MyObject myObjectA;
MyObject myObjectB;

public void someMethod() {
  synchronized (this) {
    //stuff
  }
}

public void someMethodA() {
  synchronized (myObjectA) {
    //stuff
  }
}

public void someMethodB() {
  synchronized (myObjectB) {
    //stuff
  }
}

在这种情况下:

  • someMethod阻止整个班级
  • someMethodAmyObjectA仅块
  • someMethodBmyObjectB仅块
  • someMethodA并且someMethodB可以同时调用
于 2009-03-16T09:23:54.553 回答