有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

多线程在Scala中是否可以将本地变量作为Volatile,因为在Java中这是不可能的?

据我所知,Java和Scala中标记为Volatile的字段提供了before关系

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

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

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

在Java中,如果给闭包这样的变量,它可以被另一个线程修改,因此,它必须是final,对吗


共 (1) 个答案

  1. # 1 楼答案

    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。这个BooleanRefdoNotStop的运行时表示,并包装了一个boolean。有关上一次声明的更多信息,请查看related Java documentation

    现在让我们来揭示一下:如果我们让doNotStop再次变得易变呢

    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的值在闭包中发生变化。当然,您可以像这里一样实现它,使用对(Volatile)BooleanRef的“有效最终”引用,其elem可以通过闭包自由修改