Singleton с изменчивым в java
class MyClass
{
private static volatile Resource resource;
public static Resource getInstance()
{
if(resource == null)
resource = new Resource();
return resource;
}
}
Здесь мое сомнение связано с java concurrency на практике, если вы используете летучую, безопасную публикацию (например, как только ссылка видна другому потоку, данные также доступны). Могу ли я использовать его здесь? Но если это правильно, то предположим, что thread1 теперь проверяет "ресурс", и он null, поэтому он начинает создавать объект. В то время как thread1 создает объект objet другой, т.е. thread2 приходит и начинает проверять значение "resource", а thread2 считает его нулевым (предполагая, что для создания ресурса "ресурс" требуется некоторое значительное количество времени, и поскольку thread1 еще не завершил создание, безопасная публикация не была, следовательно, недоступна для thread2), тогда он также начнет создавать объект? если да, то инвариант класса ломается. Я прав? Пожалуйста, помогите мне в понимании этого особого использования волатильности здесь.
Ответы
Ответ 1
volatile решает одну проблему, которая является проблемой видимости. Если вы пишете одну переменную , объявляемую volatile, тогда это значение будет видимым для другого потока немедленно. Как мы все знаем, у нас есть разный уровень кэша в os L1, L2, L3, и если мы пишем переменную в одном потоке, то не гарантируется быть видимым для других, поэтому, если мы будем использовать volatile, она будет записываться в прямую память и будет видна другим. Но волатильность не решает проблему атомарности, т.е. int a; a++;
небезопасна. AS существует три связанные с ним машинные инструкции.
Ответ 2
Вы правы, несколько потоков могут попытаться создать объект Resource. Volatile просто гарантирует, что если один поток обновит ссылку, все остальные потоки будут видеть новую ссылку, а не некоторую кешированную ссылку. Это медленнее, но безопаснее.
Если вам нужен только один загружаемый ресурс, вам нужно сделать что-то вроде этого:
class MyClass
{
private static volatile Resource resource;
private static final Object LOCK = new Object();
public static Resource getInstance()
{
if(resource == null) {
synchronized(LOCK) { // Add a synch block
if(resource == null) { // verify some other synch block didn't
// write a resource yet...
resource = new Resource();
}
}
}
return resource;
}
}
Ответ 3
Я знаю, что вы не спрашиваете о лучших решениях, но это определенно стоит, если вы ищете ленивое решение синглтон.
Используйте частный статический класс для загрузки singleton. Класс не загружается до вызова и поэтому ссылка не загружается до класса. Загрузка классов по реализации является потокобезопасной, и вы также несете очень небольшие накладные расходы (в случае, если вы выполняете повторяющиеся летучие нагрузки [которые все еще могут быть дешевыми], это разрешение всегда нормальных нагрузок после первоначальной конструкции).
class MyClass {
public static Resource getInstance() {
return ResourceLoader.RESOURCE;
}
private static final class ResourceLoader {
private static final Resource RESOURCE = new Resource();
}
}
Ответ 4
Я думаю, вы должны использовать ключевое слово syncronized
до getInstance
.
Для повышения производительности вы можете использовать шаблон с двойным контролем:
Ответ 5
volatile
ключевое слово гарантирует, что чтение и запись этой переменной являются атомарными.
В соответствии с tutorial
Reads and writes are atomic for all variables declared volatile
Использование изменчивых переменных снижает риск согласованности памяти ошибок, поскольку любая запись в изменчивую переменную устанавливает происходит-до отношения с последующим чтением того же переменная. Это означает, что изменения в изменчивой переменной всегда видимый для других потоков. Что еще, это также означает, что когда поток читает изменчивую переменную, он видит не только последнее изменение к летучим, но также и побочным эффектам кода, который привел изменение.
Ответ 6
При применении к полю Java volatile гарантирует, что:
-
(Во всех версиях Java) Существует глобальный порядок чтения и записывается в изменчивую переменную. Это означает, что каждая нить доступ к неустойчивому полю будет считывать его текущее значение до продолжая, а не (потенциально) используя кешированное значение. (Однако, нет никакой гарантии относительно относительного упорядочения неустойчивых чтений и пишет с обычными чтениями и письмами, что означает, что это обычно не является полезной конструкцией потоков.)
-
(В Java 5 или более поздней версии) Неустойчивые чтения и записи устанавливают происходит до отношений, подобно приобретению и выпуску мьютекс.
Подробнее информация.
Ответ 7
Вы правы, в этом случае Ресурс может быть сконструирован дважды из-за расы, которую вы описываете. Если вы хотите реализовать синглтон (без явной блокировки) в Java 5+, используйте одноэлементное перечисление, как описано в ответах на Что представляет собой эффективный способ реализации одноэлементного шаблона в Java?.
Ответ 8
прежде всего, имея такой синглтон, вы, по сути, создаете глобальный объект, который является плохой практикой. Я полагаю, вы используете Enums вместо этого.
Ответ 9
Вот мое предложение добавить летучие и синхронизированные вместе.
Примечание: нам еще нужно выполнить двойную проверку.
public class MySingleton {
private static volatile MySingleton instance;
private MySingleton() {}
synchronized private static void newInstance() {
if(instance == null) {
instance = new MySingleton();
}
}
public static MySingleton get() {
if(instance == null) {
newInstance();
}
return instance;
}
}