Подтвердить что ты не робот

Java - Непрерывная защита потоков

У меня есть вопрос относительно модели памяти Java. Вот простой класс, представляющий проблему:

public class ImmutableIntArray {

    private final int[] array;

    public ImmutableIntArray() {
        array = new int[10];
        for (int i = 0; i < 10; i++) {
            array[i] = i;
        }
    }

    // Will always return the correct value?
    public int get(int index) {
        return array[index];
    }

}

Насколько я знаю, JMM гарантирует, что значение конечных полей будет видно другим потокам после построения. Но я хочу, чтобы другие потоки увидели самую последнюю версию данных, хранящихся в массиве после построения.

Конечно, приведенный выше код - просто простой пример, представляющий проблему, на самом деле я хочу реализовать простой кеш для прямых байтовых буферов, и я не хотел бы полагаться на некоторые классы Collection. В настоящее время я использую ReentrantReadWriteLock для обеспечения правильного поведения, но я бы хотел избежать его, если это возможно.

4b9b3361

Ответ 1

В этом примере все будут в порядке (хм, пусть немного приостановит суждение). Неизменность - это амброзия, когда дело касается безопасности потоков - если значение не может измениться, большинство проблем concurrency сразу перестают быть проблемой.

Amir упоминается volatile, что обычно полезно, но конструктор также имеет схожую семантику для переменных final, которые обеспечивают видимость. Подробнее см. JLS 17.5 - по существу, конструктор формирует связь между прошлыми и конечными переменными и последующими чтениями. p >

EDIT. Таким образом, вы устанавливаете ссылку values ​​ для массива в конструкторе, она видима для всех потоков в этой точке, а затем она не изменяется. Поэтому мы знаем, что все другие потоки будут видеть один и тот же массив. Но как насчет содержимого массива?

Как бы то ни было, элементы массива не имеют специальной семантики относительно волатильности, они как будто вы просто объявили класс себе что-то вроде:

public class ArrayTen {
    private int _0;
    private int _1;
    // ...
    private int _9;

    public int get(int index) {
       if (index == 0) return _0;
       // etc.
    }
}

Итак, другой поток увидит эти переменные только в том случае, если мы сможем что-то сделать, чтобы установить связь между событиями. И если мое понимание правильное, это требует небольшого изменения исходного кода.

Мы уже знаем, что установка ссылки на массив происходит до конца конструктора. Дополнительная точка, которая всегда верна, заключается в том, что действия в одном потоке происходят - до более поздних действий в том же потоке. Поэтому мы можем комбинировать их, сначала устанавливая поля массива, а затем назначая конечное поле, чтобы получить эту транзитивную гарантию видимости. Конечно, это потребует временную переменную:

public class ImmutableIntArray {

    private final int[] array;

    public ImmutableIntArray() {
        int[] tmp = new int[10];
        for (int i = 0; i < 10; i++) {
            tmp[i] = i;
        }
        array = tmp;
    }

    // get() etc.
}

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

Но опять-таки может быть что-то еще, что я пропустил, что означает, что гарантии concurrency не столь надежны, как надеялись. Этот вопрос, на мой взгляд, отличный пример того, почему писать пуленепробиваемый многопоточный код сложно, даже если вы думаете, что делаете что-то очень простое, и как нужно много думать и осторожно (а затем и исправлять ошибки), чтобы получить право.

Ответ 2

ваш пример не совсем прав. чтобы получить окончательное полевое обеспечение, вам необходимо:

public ImmutableIntArray() {
    int tmparray = new int[10];
    for (int i = 0; i < 10; i++) {
        tmparray[i] = i;
    }
    array = tmparray;
}

Ответ 3

Я действительно думаю, что вам предоставлена ​​та же семантика с массивом, что и с окончательной ссылкой на объект. Spec указывает

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

В нем также говорится

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

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.5

Ответ 4

Я думаю, что ваши изменения массива будут видны с вашим ImmutableIntArray. Из моего чтения в JLS действие [freeze] должно выполняться при выходе из конструктора. Использование массива temp, который я считаю бесполезным:

int tmparray = new int[10];
for (int i = 0; i < 10; i++) {
    tmparray[i] = i;
}
array = tmparray;

Чтобы получить окончательные полевые гарантии, нам понадобится [заморозить] где-нибудь перед выходом из конструктора:

int tmparray = new int[10];
for (int i = 0; i < 10; i++) {
    tmparray[i] = i;
}
array = tmparray;
[freeze]

Во всяком случае, [freeze] оставляет ворота открытыми, чтобы переупорядочить инструкции над ним, поэтому у нас будет одно и то же:

int tmparray = new int[10];
array = tmparray; 
for (int i = 0; i < 10; i++) {
    tmparray[i] = i;
}
[freeze]

[freeze] реализовано, чтобы содержать как минимум [StoreStore]. Этот барьер StoreStore должен быть выпущен до того момента, когда будет опубликован экземпляр.

Из JSR-133 Cookbook:

Вы не можете перемещать магазины финалов внутри конструкторов ниже магазина вне конструктора, который может сделать объект видимым для других потоков. (Как видно ниже, это может также потребовать выдачи барьера). Точно так же вы не можете изменить порядок первого из двух с третьим назначением:       v.afield = 1; x.finalField = v;...; sharedRef = x;

И я думаю, что это делается (JSR-133 Cookbook):

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

Таким образом, мы не можем хранить в sharedRef до того, как будут выполнены все остальные хранилища contructor.

Вы можете искать по: "Транзитивные гарантии из конечных полей" в (спецификация JSR133).