【问题标题】:Equivalent code for instance method synchronization in JavaJava中实例方法同步的等价代码
【发布时间】:2010-09-29 20:36:01
【问题描述】:

在讨论 Java 同步 question 时,有人评论说以下 sn-ps 不等效(并且可能编译为不同的字节码):

public synchronized void someMethod() {
  //stuff
}

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

它们是等价的吗?

【问题讨论】:

    标签: java synchronization


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      通过这种方式,您可以同步整个方法,同时,使用同步块样式将其与另一个方法中的代码 sn-p 同步。

      【讨论】:

      • 你是对的,如果类方法是静态的,那么锁将在类对象而不是实例对象上。但是这里不是这样,它是一个实例方法。
      【解决方案3】:

      我最初的评论是声明是相同的。

      在这两种情况下,首先发生的事情是调用线程将尝试获取当前对象的(意思是this')监视器。

      我不知道不同的字节码,我很高兴听到其中的区别。但实际上,它们是 100% 相同的。

      编辑:我要澄清这一点,因为这里有些人弄错了。考虑:

      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(),即使它未同步。

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

      【讨论】:

      • Eljenso 说你说它们不相等是不是错了?
      • 不,有人声称他们不相等......我自己不明白他是如何得出这个结论的:)
      • 我不太清楚这里的争议是什么。他们确实编译成不同的东西。这意味着它们不是 100% 相同的。
      【解决方案4】:

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

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

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

      【讨论】:

      【解决方案5】:

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

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

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

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

      【讨论】:

      • 在方法上同步不是方法签名的一部分。
      • 我认为你在歪曲我的话。正如我所说,它是该方法定义的一部分。编译器在该方法的标头上设置 ACC_SYNCHRONIZED 标志,以指示 VM 在调用该方法时应该获取监视器。
      • +1 好答案,指出它是“逻辑上等价的”,没有提到方法签名。
      【解决方案6】:
      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
      • someMethodAsomeMethodB可以同时调用

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-05-21
          • 1970-01-01
          • 1970-01-01
          • 2015-06-09
          • 1970-01-01
          • 2011-12-01
          • 1970-01-01
          相关资源
          最近更新 更多