首页 > 解决方案 > 我可以在 Scala 中将本地 var 作为 Volatile,因为在 Java 中这是不可能的吗?

问题描述

据我所知,Java 和 Scala 中标记为 Volatile 的字段提供了发生之前的关系。

在 Java 中,方法中的局部变量不可能是 volatile。然而,Scala 编译器似乎允许这样的事情,如下面的代码:

def test: Unit = {
  @volatile var doNotStop = true 
}

它实际上与Java中的工作方式相同吗?这种代码的语义是什么?它在运行时在字节码和 JVM 中的外观如何?

在Java中,如果给闭包这样的变量,可以由另一个线程修改,因此,它是强制性的,对吧?

标签: javamultithreadingscalajvm

解决方案


TL;DR@volatile注释在应用于局部变量时看起来像是被忽略了,除非变量可以在闭包内从这个局部范围内转义。

为了确保这一点,我们可以检查与以下代码片段对应的字节码

class Foo {
    def test: Unit = {
      @volatile var doNotStop: Boolean = true 
    }
}

使用 获得的类文件scalac可以使用javap -c -v -p. 这是该test方法的相关部分:

public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=1
         0: iconst_1
         1: istore_1
         2: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            1       1     1 doNotStop   Z
      ...

请注意,没有与任何易失性访问相关的信息。

如果我们选择声明doNotStop为实例变量,则javap显示以下带有明确 volatile 标志的字段声明:

private volatile boolean doNotStop;
  descriptor: Z
  flags: ACC_PRIVATE, ACC_VOLATILE

但是,您对局部变量超出其范围的担忧是完全正确的!让我们试试这个:

class Foo {
    def test = {
        var doNotStop: Boolean = true
        () => doNotStop = false
    }
}

使用javap -p(这次无需查看字节码或标志)为我们提供了以下定义:

public class Foo {
  public scala.Function0<scala.runtime.BoxedUnit> test();
  public static final void $anonfun$test$1(scala.runtime.BooleanRef);
  public Foo();
  private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda);
}

你可以看到闭包已经被编译成它自己的方法$anonfun$test$1,名为BooleanRef. 这BooleanRef是运行时表示doNotStop并包装了boolean. 有关最后一个声明的更多信息,您可以查看相关的 Java 文档

现在揭示:如果我们doNotStop再次使 volatile 发生怎么办?

public class Foo {
  public scala.Function0<scala.runtime.BoxedUnit> test();
  public static final void $anonfun$test$1(scala.runtime.VolatileBooleanRef);
  public Foo();
  private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda);
}

课程基本保持不变,但$anonfun$test$1现在需要一个VolatileBooleanRef. 猜猜它的内部boolean是如何实现的:

volatile public boolean elem;

这里的语义非常清楚:您的非局部Boolean变量在运行时表示为BooleanRef实例的字段。volatile注释可以标记的正是该字段。你去了,@volatile毕竟在那里很有用!

要回答您的第二个问题:Java 的闭包仅关闭“有效最终”的值,这将不允许这种模式在doNotStop闭包内更改值。当然,您可以使用与此处相同的方式来实现它,使用对 a 的“有效最终”引用,(Volatile)BooleanRefelem可以由闭包自由修改。


推荐阅读