Семантика финальных полей в потоках

Это из JLS 17.5:

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

Обсуждение в JLS 17.5 включает этот пример кода:

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; // guaranteed to see 3
            int j = f.y; // could see 0
        }
    }
}

Я попытался повторно использовать этот код, чтобы воспроизвести описанную выше ситуацию, и вот что у меня есть:

public class FinalFieldThread extends Thread {

    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();

        threadB.start();
        threadA.start();
        //threadB.start();

    }
}

class ThreadA extends Thread {

    @Override
    public void run() {
        System.out.println("ThreadA");
        FinalFieldExample.writer();
    }
}

class ThreadB extends Thread {

    @Override
    public void run() {
        System.out.println("ThreadB");
        FinalFieldExample.reader();
    }
}

Я могу проверить, как final читается правильно, но как я могу реплицировать, когда он не читается правильно (то есть когда есть ссылка на протектор до того, как конструктор закончил?)


person Isaac    schedule 29.02.2012    source источник


Ответы (1)


Что ты ищешь

То, что вы пытаетесь проверить, называется Не публиковать ссылку "это" во время строительства или Опасность видимости. Прочтите следующие ссылки в том порядке, в котором они указаны.

Чтение

  1. Теория и практика Java: методы безопасного строительства
  2. JSR 133 (модель памяти Java): часто задаваемые вопросы
  3. Выполните Гарантии «актуальности» значений последних полей Java распространяются на косвенные ссылки?

Образец кода

class FinalField
{
    final int x;
    int y;

    public FinalField()
    {
        Thread t = new Thread(new TestThread(this));
        t.start();

        y = 4;
        x = 3;
    }
}

class TestThread implements Runnable
{
    FinalField f;
    TestThread(FinalField f)
    {
        if(f.x != 3)
            System.out.println("value of x = " + f.x);

        this.f = f;
    }

    public void run() 
    {
        if(f.x != 3)
            System.out.println("value of x = " + f.x);
    }
}

public class Test
{
    public static void main(String[] args) 
    {
        for(int i=0; i<100; i++)
        {
            new FinalField();
        }
    }
}

Output

value of x = 0
value of x = 0
value of x = 0
.
.
.
value of x = 0
value of x = 0
value of x = 0

Пояснение к выводу

Когда я обращаюсь к последнему полю в конструкторе своего потока, тогда поле final x не было должным образом инициализировано, и поэтому мы получаем 0. Whereas, когда я обращаюсь к тому же полю в run(), тогда к тому времени поле final x инициализируется значением 3. Это происходит из-за escaping ссылки на объект FinalField. Прочтите 1-ю ссылку, которой я поделился. намного подробнее.

person Favonius    schedule 29.02.2012
comment
Поместите public FinalField() { Thread t = new Thread(new estThread(this)); t.start(); try { Thread.sleep(200); } catch (InterruptedException e) { } y = 4; x = 3; } в конструктор, и вы получите x быть 0 в методе запуска, я не понимаю, в чем разница в вашем примере между конечным и не конечным полями - person Jaime Hablutzel; 01.08.2012
comment
Это очевидно, когда вы сказали t.start();, тогда вы запустили новый поток, но когда вы вызываете Thread.sleep(200);, вы вызываете его в основном потоке (или потоке, который создал тестовый поток). Теперь, когда ваш основной поток ожидает, происходит промежуточное выполнение тестового потока (который, в свою очередь, вызывает run). Теперь, до этого момента последнее поле не инициализируется, и, следовательно, вы получаете x=0, что известно как экранирование this. - person Favonius; 01.08.2012
comment
Теперь то, что вы пытаетесь сделать, - это намеренно ввести задержку в основном потоке, из-за которой другой поток мог бы видеть неинициализированное поле final. В приведенном выше вопросе говорится о правильной модели использования полей final, а также о многопоточной среде. - person Favonius; 01.08.2012
comment
хорошо, я понял вашу точку зрения, но я до сих пор не вижу никакой разницы в поведении полей final и non final, здесь: `Если это будет выполнено, то когда объект будет виден другим потоком, этот поток всегда будет видеть правильно построенная версия последних полей этого объекта. `JLS определяет конечные поля и некоторым образом отличает их от незавершенных полей ... вы поняли мою точку зрения? - person Jaime Hablutzel; 02.08.2012
comment
Вы всегда должны инициализировать поля final либо во время объявления, либо в конструкторе класса. Теперь рассмотрим случай неизменяемого класса. Если я пропущу this при построении объекта, тогда будут две версии неизменяемого объекта 1) Правильно построенный и 2) Неправильно построенный. Это противоречит философии неизменяемых объектов. Кроме того, для данного класса последнее поле должно иметь уникальное значение в течение всего времени его существования, чего не происходит в случае утечки this. - person Favonius; 02.08.2012
comment
Если последнее поле является объектом, а не примитивом, то гарантируется ли, что обновление этого последнего поля одним потоком будет правильно отображаться другим потоком без какой-либо дополнительной синхронизации? - person AKS; 30.08.2013
comment
@Favonius Извините за мой наивный вопрос, но почему бы не принудительно во время компиляции не использовать this в конструкторе? - person Sundeep; 06.12.2013
comment
@Sundeep - проверьте это docs.oracle.com/javase/tutorial/java /javaOO/thiskey.html - person Favonius; 06.12.2013