Спецификация Java 11: 17.5. Семантика final полей

Поля, объявленные final, инициализируются один раз, но никогда не изменяются при нормальных обстоятельствах. Подробная семантика final полей несколько отличается от семантики обычных полей. В частности, компиляторы могут свободно перемещать операции чтения полей final через барьеры синхронизации и вызовы произвольных или неизвестных методов. Соответственно, компиляторам разрешено сохранять значение конечного поля в кэше в регистре и не перезагружать его из памяти в ситуациях, когда необходимо перезагрузить не-final поле.

Поля final также позволяют программистам реализовать потокобезопасные неизменяемые объекты без синхронизации. Поточно-безопасный неизменяемый объект рассматривается как неизменный для всех потоков, даже если для передачи ссылок на неизменяемый объект между потоками используется гонка данных. Это может обеспечить гарантии безопасности от неправильного использования неизменяемого класса неправильным или вредоносным кодом. Поля final должны использоваться правильно, чтобы гарантировать неизменность.

Считается, что объект полностью инициализирован, когда его конструктор завершает работу. Поток, который может видеть ссылку на объект только после того, как этот объект был полностью инициализирован, гарантированно увидит правильно инициализированные значения для final полей этого объекта.

Модель использования полей final проста: установите поля final для объекта в конструкторе этого объекта; и не записывайте ссылку на создаваемый объект в месте, где другой поток может его увидеть до того, как конструктор объекта будет завершен. Если это будет выполнено, то когда объект будет виден другим потоком, этот поток всегда будет видеть правильно сконструированную версию final полей этого объекта. Он также будет видеть версии любого объекта или массива, на которые ссылаются эти final поля, которые, по крайней мере, так же актуальны, как и final поля.

Пример - final поля в модели памяти Java

В приведенной ниже программе показано, как поля final сравниваются с обычными полями.

class FinalFieldExample { 
    final int x;
    int y; 
    static FinalFieldExample f;

    public FinalFieldExample() {
        x = 3; 
        y = 4; 
    } 

    static void writer() {
        f = new FinalFieldExample();
    }

    static void reader() {
        if (f != null) {
            int i = f.x;  // гарантированно увидим 3  
            int j = f.y;  // мог видеть 0
        } 
    } 
}

Класс FinalFieldExample имеет final поле int x и не-final поле int y. Один поток может выполнять метод writer, а другой - метод reader.

Поскольку метод writer записывает f после завершения конструктора объекта, метод reader гарантированно увидит правильно инициализированное значение для f.x: он прочитает значение 3. Однако f.y не является final; поэтому не гарантируется, что метод reader увидит для него значение 4.

Пример - final поля для безопасности

Поля final предназначены для обеспечения необходимых гарантий безопасности. Рассмотрим следующую программу. Один поток (который мы будем называть потоком 1) выполняет:

Global.s = "/tmp/usr".substring(4);

в то время как другой поток (поток 2) выполняет

String myS = Global.s; 
if (myS.equals("/tmp")) System.out.println(myS);

Строковые объекты должны быть неизменными, а строковые операции не выполняют синхронизацию. В то время как реализация String не имеет никаких гонок данных, другой код может иметь гонку данных, включающую использование объектов String, а модель памяти дает слабые гарантии для программ, которые имеют гонку данных. В частности, если бы поля класса String не были final, тогда было бы возможно (хотя маловероятно), что поток 2 мог бы первоначально увидеть значение по умолчанию 0 для смещения строкового объекта, позволяя ему сравнивать как равное "/tmp". Более поздняя операция с объектом String может увидеть правильное смещение 4, так что объект String будет восприниматься как "/usr". Многие функции безопасности языка программирования Java зависят от того, что объекты String воспринимаются как действительно неизменяемые, даже если вредоносный код использует гонки данных для передачи ссылок на String между потоками.


Читайте также:


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки