В буквальном смысле ваш вопрос: «… верно ли мое предположение в том, что виноваты JIT-оптимизации? », ответ - да, очень вероятно, что JIT-оптимизации ответственны за такое поведение в этой спецификации. c пример.
Но так как изменение полей static final
полностью не соответствует спецификации, существуют другие вещи, которые могут нарушить его аналогичным образом. Например, JMM не имеет определения видимости таких изменений в памяти, следовательно, совершенно не определено, замечают ли другие потоки такие изменения или нет. Они даже не обязаны замечать это последовательно, то есть они могут использовать новое значение, а затем снова использовать старое значение, даже при наличии примитивов синхронизации.
Хотя JMM и оптимизатор трудно В любом случае, разделите здесь.
Ваш вопрос «… это те вопросы, которые ограничены только stati c только конечными полями? », ответить на него гораздо труднее, поскольку оптимизации, конечно, не ограничиваются 1012 * полей, но поведение, например, нестатических c final
полей, не одинаково и имеет различия между теорией и практикой.
Для не статичных c final
поля, модификации через Reflection допускаются при определенных обстоятельствах. На это указывает тот факт, что setAccessible(true)
достаточно, чтобы сделать возможной такую модификацию, без взлома экземпляра Field
для изменения внутреннего поля modifiers
.
В спецификации говорится :
17.5.3. Последующая модификация final
полей
В некоторых случаях, таких как десериализация, системе потребуется изменить поля final
объекта после построения. final
поля могут быть изменены с помощью отражения и других зависящих от реализации средств. Единственный шаблон, в котором это имеет разумную семантику, это шаблон, в котором объект создается, а затем обновляются поля final
объекта. Объект не должен быть видимым для других потоков, а также поля final
не должны читаться, пока все обновления полей final
объекта не будут завершены. Замораживание поля final
происходит как в конце конструктора, в котором установлено поле final
, так и сразу после каждой модификации поля final
с помощью отражения или другого специального механизма.
…
Другая проблема заключается в том, что спецификация допускает агрессивную оптимизацию полей final
. Внутри потока допустимо переупорядочивать чтение поля final
с теми модификациями поля final
, которые не имеют места в конструкторе.
Пример 17.5.3-1. Агрессивная оптимизация final
полей class A {
final int x;
A() {
x = 1;
}
int f() {
return d(this,this);
}
int d(A a1, A a2) {
int i = a1.x;
g(a1);
int j = a2.x;
return j - i;
}
static void g(A a) {
// uses reflection to change a.x to 2
}
}
В методе d
компилятору разрешено свободно изменять порядок чтения x
и вызова g
. Таким образом, new A().f()
может вернуть -1
, 0
или 1
.
На практике, определение правильных мест, где возможна агрессивная оптимизация, не нарушая юридические сценарии ios, описанные выше, являются открытым вопросом , поэтому, если не указано -XX:+TrustFinalNonStaticFields
, JVM HotSpot не будет оптимизировать не * stati c final
поля так же, как static final
fields.
Конечно, когда вы не объявляете поле как final
, JIT не может предполагать, что оно никогда не изменится, хотя, в отсутствие примитивов синхронизации потоков, он может учитывать фактические изменения, происходящие в пути кода, который он оптимизирует (включая отражающие). Таким образом, он все еще может активно оптимизировать доступ, но только , как если бы чтения и записи, все еще происходили в порядке программы в потоке выполнения. Таким образом, вы заметите оптимизации только при взгляде на него из другого потока без правильных конструкций синхронизации.