Безопасная публикация стандартной коллекции, не поддерживающей потокобезопасность

Правильно ли я понимаю, что следующий фрагмент кода безопасен с точки зрения публикации стандартной коллекции, которая по своей сути не является потокобезопасной (HashSet в этом примере), потому что ни один из потоков не изменяет коллекцию после барьера памяти:

class C {
    private final Set<String> strings;

    C(final Set<String> strings) {
        this.strings = new HashSet<>(strings);
    }

    void doSmthAsync() {
        new Thread() {
            @Override
            public void run() {
                for (final String s : strings) {
                    System.out.println(s);
                }
            }
        }.start(); // Disregard the 2nd memory barrier caused by Thread.start()
    }
}

а ниже нет?

class C {
    private final Set<String> strings = new HashSet<>();

    C(final Set<String> strings) {
        this.strings.addAll(strings);
    }

    void doSmthAsync() {
        new Thread() {
            @Override
            public void run() {
                for (final String s : strings) {
                    System.out.println(s);
                }
            }
        }.start(); // Disregard the 2nd memory barrier caused by Thread.start()
    }
}

Обновление: приведенные выше примеры не совсем корректны, поскольку на самом деле существует два барьера памяти, а не один:

  • инициализация конечного поля
  • Thread.start()

Если вместо этого в методе doSmthAsync() мы передаем коллекцию уже запущенному потоку (например, одному из фонового пула потоков), кажется, что поток-потребитель может увидеть финальное поле в том состоянии, в котором оно было во время инициализации (т. е. пустой набор в 2-й случай).


person Bass    schedule 29.12.2015    source источник


Ответы (2)


Поваренная книга JSR-133

http://gee.cs.oswego.edu/dl/jmm/cookbook.html

Хранилище конечного поля (внутри конструктора) и, если поле является ссылкой, любое хранилище, на которое может ссылаться этот финал, не может быть переупорядочено с последующим хранилищем (вне этого конструктора) ссылки на объект, содержащий это поле, в переменная, доступная другим потокам. Например, вы не можете изменить порядок x.finalField = v; ... ; общая ссылка = х; Это вступает в игру, например, при встраивании конструкторов, где "..." охватывает логический конец конструктора. Вы не можете перемещать хранилища финалов внутри конструкторов ниже хранилища вне конструктора, что может сделать объект видимым для других потоков.

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

person Vladimir Dolzhenko    schedule 31.12.2015

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


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

Кстати, ваши примеры - это два способа написать одно и то же, поэтому они так же потокобезопасны, как и друг друга.

person Peter Lawrey    schedule 29.12.2015
comment
Плохо, пожалуйста, посмотрите обновление моего вопроса и не обращайте внимания на 2-й барьер, вызванный Thread.start(). - person Bass; 29.12.2015