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
. Для получения дополнительной информации о последнем объявлении вы можете обратиться к связанной документации Java .
Теперь для раскрытия: что если мы снова сделаем 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
может быть свободно изменен закрытием.