о статических инициализаторах, которые являются потокобезопасными и выполняются одним потоком во время загрузки класса, почему я должен использовать "закрытое конечное статическое поле" в синглетоне, если "личное статическое поле" позволяет мне понять те же поточные и надежные гарантии видимости - при условии, что статическое поле не будет затронуто после статической инициализации.
Просто я говорю, что следующие два примера, приведенные ниже, ведут себя одинаково в многопоточной среде: каждый раз, когда какой-либо клиент получит тот же экземпляр - нет даже незначительной разницы - согласны ли вы? Пожалуйста, дайте мне знать в любом случае.
Пример статического поля:
public class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
private Singleton() {
}
}
Пример конечного статического поля:
public class Singleton_final {
private final static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
private Singleton() {
}
}
С наилучшими пожеланиями, Германн
Как и во многих проблемах многопоточности, это немного тонко. Позвольте мне немного разобраться в этом вопросе, а затем дать вам ответ.
Если клиент видит null
ссылку non-, они будут видеть только один Singleton
вы создаете. На этом нет вопросов. Вот два вопроса:
null
?Singleton
? Последний вопрос не совсем уместен для вашего дела, потому что у Singleton
нет никакого состояния. Но если у него было состояние, и это состояние было сохранено в non- final
полях, то это было бы проблемой. Например, учитывая:
public class Singleton {
private /* non-final */ String name;
private Singleton(String name) {
this.name = name;
}
}
... частично построенный Singleton
- это тот, чье имя равно null
(несмотря на то, что во время построения было установлено значение не -null
).
То, что вы хотите, вероятно, (а), что никто не видит null
и (б), что никто не видит частично построенный объект. Чтобы получить это, вам нужно, чтобы класс был свободен от гонок данных. Для этого вам нужны отношения "дожидаться".
Таким образом, на самом деле возникает вопрос: существует ли связь между потоком, инициализирующим instance
, и любой поток, который позже его читает?
JLS немного в этом случае. Там детальное описание инициализации класса в JLS 12.4.2, которое включает в себя блокировку в классе и, таким образом, вводит фронт, предшествующий началу. Но в JLS нет ничего, чтобы указать, что происходит, когда класс уже инициализирован! В этих случаях JLS не требует блокировки и, следовательно, не устанавливает каких-либо связей между ними. Строгое чтение JLS предполагает, что клиенты в других потоках могут видеть null
ссылочный или частично построенный объект.
JLS намекает, что этого не должно быть в 12.4.1:
Цель состоит в том, что класс или тип интерфейса имеет набор инициализаторов, которые помещают его в согласованное состояние и что это состояние является первым состоянием, которое наблюдается другими классами.
Что ж, это здорово, что это "намерение", но там ничего (кроме этого заявления о намерениях) не требуется.
final
поле (статическое или нет) получает специальную семантику безопасности потока (JLS 17.5 или см. Этот вопрос на SO), который по существу обеспечивает вышеупомянутый край, предшествующий фронту, и, таким образом, устраняет гонку данных и обеспечивает null
ссылку non- на полностью построенный объект.
В случае кода, который вы указали, нет никакой разницы, потому что у вас нет метода фактического установки нового экземпляра класса Singleton
потому что у вас нет каких-либо конструкторов или сеттеров, видимых вне класса.
Однако это не означает, что без final
можно было бы изменить экземпляр Singleton
. API Reflection можно теоретически использовать для изменения экземпляра, если у вас не было final
квалификатора. Я говорю теоретически, потому что я никогда не пробовал это сам, и я не могу придумать путь от головы. Я уступаю любому, у кого есть немного больше опыта с API Reflection