Является ли это ошибкой в ​​Scala 2.9.1 ленивой реализации или просто артефактом декомпиляции

Я рассматриваю возможность использования Scala в довольно интенсивной вычислительной программе. Профилирование версии нашего кода на С++ показывает, что мы могли бы извлечь большую пользу из оценки Lazy. Я пробовал это в Scala 2.9.1, и мне очень нравится. Однако, когда я запускал класс через декомпилятор, реализация не выглядела совершенно правильно. Я предполагаю, что это артефакт декомпилятора, но я хотел получить более убедительный ответ...

рассмотрим следующий тривиальный пример:

class TrivialAngle(radians : Double) 
{
    lazy val sin = math.sin(radians)
}

когда я декомпилирую его, я получаю следующее:

import scala.ScalaObject;
import scala.math.package.;
import scala.reflect.ScalaSignature;

@ScalaSignature(bytes="omitted")
public class TrivialAngle
  implements ScalaObject
{
  private final double radians;
  private double sin;
  public volatile int bitmap$0;

  public double sin()
  {
    if ((this.bitmap$0 & 0x1) == 0);
    synchronized (this)
    {
      if (
        (this.bitmap$0 & 0x1) == 0)
      {
        this.sin = package..MODULE$.sin(this.radians);
        this.bitmap$0 |= 1; 
      } 
      return this.sin;
    }
  }

  public TrivialAngle(double radians)
  {
  }
}

Для меня возвратный блок находится в неправильном месте, и вы всегда будете получать блокировку. Это не может быть то, что делает настоящий код, но я не могу это подтвердить. Может ли кто-либо подтвердить или опровергнуть, что у меня есть фиктивная декомпиляция, и что ленивая реализация несколько разумна (т.е. Блокируется только при вычислении значения и не получает блокировку для последующих вызовов?)

Спасибо!

Для справки, это декомпилятор, который я использовал: http://java.decompiler.free.fr/?q=jdgui

Ответ 1

scala -Xprint:jvm показывает истинную историю:

[[syntax trees at end of jvm]]// Scala source: lazy.scala
package <empty> {
  class TrivialAngle extends java.lang.Object with ScalaObject {
    @volatile protected var bitmap$0: Int = 0;
    <paramaccessor> private[this] val radians: Double = _;
    lazy private[this] var sin: Double = _;
    <stable> <accessor> lazy def sin(): Double = {
      if (TrivialAngle.this.bitmap$0.&(1).==(0))
        {
          TrivialAngle.this.synchronized({
            if (TrivialAngle.this.bitmap$0.&(1).==(0))
              {
                TrivialAngle.this.sin = scala.math.`package`.sin(TrivialAngle.this.radians);
                TrivialAngle.this.bitmap$0 = TrivialAngle.this.bitmap$0.|(1);
                ()
              };
            scala.runtime.BoxedUnit.UNIT
          });
          ()
        };
      TrivialAngle.this.sin
    };
    def this(radians: Double): TrivialAngle = {
      TrivialAngle.this.radians = radians;
      TrivialAngle.super.this();
      ()
    }
  }
}

Это (с JVM 1.5) безопасный и очень быстрый двойной блокировки.

Подробнее:

Какова (скрытая) стоимость lazy val Scala?

Имейте в виду, что если у вас есть несколько ленивых членов val в классе, только один из них может быть инициализирован сразу, поскольку они защищены synchronized(this) { ... }.

Ответ 2

То, что я получаю с javap -c, не соответствует вашему декомпилированию. В частности, при обнаружении поля в качестве инициализированного монитора не появляется монитор. Версия 2.9.1 тоже. По-прежнему существует барьер памяти, подразумеваемый волатильным доступом, поэтому он не приходит совершенно бесплатно. Комментарии, начинающиеся с ///, являются моими

public double sin();
  Code:
   0:   aload_0
   1:   getfield        #14; //Field bitmap$0:I
   4:   iconst_1
   5:   iand
   6:   iconst_0
   7:   if_icmpne       54 /// if getField & 1 == O goto 54, skip lock
   10:  aload_0
   11:  dup
   12:  astore_1
   13:  monitorenter
            /// 14 to 52 reasonably equivalent to synchronized block 
            /// in your decompiled code, without the return
   53:  monitorexit
   54:  aload_0
   55:  getfield        #27; //Field sin:D
   58:  dreturn        /// return outside lock
   59:  aload_1        /// (this would be the finally implied by the lock)
   60:  monitorexit
   61:  athrow
  Exception table:
   from   to  target type
    14    54    59   any