据我所知,Java和Scala中的字段标记为Volatile,提供了在关系之前发生的事情。
在Java中,不可能在方法中将局部变量设置为volatile。然而,Scala编译器似乎允许这样的事情,如下面的代码所示:
def test: Unit = {
@volatile var doNotStop = true
}
它的实际工作方式与Java相同吗?这些代码的语义是什么?在运行期间它如何看待字节代码和JVM?
在Java中,如果赋予闭包这样的变量可以被另一个线程修改,因此,它必须是最终的,对吧?
答案 0 :(得分:6)
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
会显示以下带有清晰易失标记的字段声明:
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
。有关上一个声明的更多信息,您可以查看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的闭包只关闭了有效的最终值#34;这将禁止这种模式,其中doNotStop
的值在闭包内发生变化。你当然可以像在这里一样使用&#34;有效的最终&#34;来实现它。对(Volatile)BooleanRef
的引用,elem
可以由闭包自由修改。