окончательные поля и безопасность потоков

#java #thread-safety #immutability

Вопрос:

Должны ли это быть все поля, включая суперполя, намеренно неизменяемого класса java «final», чтобы быть потокобезопасными, или достаточно не иметь методов-модификаторов?

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

Комментарии:

1. Что вы подразумеваете под отключением методов модификатора методы модификатора? Исключение из всех сеттеров? Это нарушило бы принцип подстановки Лискова . Но да, этот класс был бы потокобезопасным.

2. Да, создание исключения во время выполнения или переопределение их пустым телом, возможно, с помощью некоторого ведения журнала. Я знаю, что это нарушает LSP.

Ответ №1:

Чтобы эффективно использовать неизменяемый объект без final полей потокобезопасным способом, вам необходимо использовать один из безопасных идиом публикации при предоставлении объекта другим потокам после инициализации, в противном случае эти потоки могут видеть объект в частично инициализированном состоянии (на практике из параллелизма Java).:

  • Инициализация ссылки на объект из статического инициализатора;
  • Сохранение ссылки на него в изменчивом поле или атомарной ссылке;
  • Сохранение ссылки на него в конечном поле правильно построенного объекта; или
  • Сохранение ссылки на него в поле, которое должным образом защищено блокировкой.

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

Обратите внимание, что если ваш объект реализует интерфейс , вы можете использовать подход, используемый Collections.unmodifiableList() и т. Д:

 class ImmutableFooWrapper implements IFoo {
    private final IFoo delegate; // final provides safe publication automatically

    public ImmutableFooWrapper(IFoo delegate) {
        this.delegate = delegate;
    }
    ...
}

public IFoo immutableFoo(IFoo foo) {
    return new ImmutableFooWrapper(foo);
}
 

Комментарии:

1. Спасибо за ваш подробный ответ. Я думал об одном и том же решении с использованием делегата, но затем я всегда возвращался к решению с расширением, потому что, я думаю, ссылка «супер» также является окончательной. Чего мне не хватает?

2. @pcjuzer: super не имеет ничего общего с параллелизмом, так что в случае расширения вам все равно понадобится безопасная публикация.

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

Ответ №2:

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

Комментарии:

1. Почему поля на самом деле должны быть закрытыми?