Какой смысл делать одиночный экземпляр volatile при использовании двойной блокировки?
private volatile static Singleton uniqueInstance
В одноэлементном режиме при использовании метода двойной блокировки для синхронизации почему единственный экземпляр, объявленный как изменчивый? Могу ли я достичь такой же функциональности, не объявляя ее нестабильной?
Ответы
Ответ 1
Без volatile
код работает некорректно с несколькими потоками.
Из Википедии Двойная проверка блокировки:
По состоянию на J2SE 5.0 эта проблема исправлена. Теперь ключевое слово volatile гарантирует, что несколько потоков обрабатывают экземпляр singleton правильно. Эта новая идиома описана в Декларация с двойной проверкой блокировки:
// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
Helper result = helper;
if (result == null) {
synchronized(this) {
result = helper;
if (result == null) {
helper = result = new Helper();
}
}
}
return result;
}
// other functions and members...
}
В общем, вам следует избегать блокировки с двойной проверкой, если это возможно, так как это трудно сделать правильно, и если вы ошибетесь, это может быть трудно найти ошибку. Вместо этого попробуйте этот простой подход:
Если вспомогательный объект является статическим (по одному для каждого загрузчика классов), альтернативой является инициализация по требованию владельца идиомы
// Correct lazy initialization in Java
@ThreadSafe
class Foo {
private static class HelperHolder {
public static Helper helper = new Helper();
}
public static Helper getHelper() {
return HelperHolder.helper;
}
}
Ответ 2
volatile
предотвращает переупорядочение записей в памяти, что делает невозможным чтение других потоков неинициализированных полей вашего синглтона с помощью одноточечного указателя.
Рассмотрим эту ситуацию: поток A обнаруживает, что uniqueInstance == null
, блокирует, подтверждает, что он все еще null
, и вызывает singleton constructor. Конструктор делает запись в член XYZ
внутри Singleton и возвращает. Thread A теперь записывает ссылку на вновь созданный синглтон в uniqueInstance
и готовится к его блокировке.
Так же, как поток A готов к выпуску блокировки, поток B приходит и обнаруживает, что uniqueInstance
не null
. Тема B
обращается к uniqueInstance.XYZ
, думая, что она была инициализирована, но из-за того, что CPU переупорядочил записи, данные, которые поток A записал в XYZ
, не были видимыми для потока B. Следовательно, поток B видит неправильный значение внутри XYZ
, что неверно.
Когда вы отмечаете uniqueInstance
volatile, вставляется барьер памяти. Все записи, инициированные до uniqueInstance
, будут завершены до изменения uniqueInstance
, предотвращая описанную выше ситуацию переупорядочения.
Ответ 3
Чтобы избежать использования двойной блокировки или летучих я использую следующие
enum Singleton {
INSTANCE;
}
Создание экземпляра прост, ленив загружен и потокобезопасен.
Ответ 4
Вы можете использовать следующий код:
private static Singleton uniqueInstance;
public static synchronized Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance
}
Ответ 5
Запись в поле volatile произойдет до любой операции чтения.
Ниже приведен пример кода для лучшего понимания:
private static volatile ResourceService resourceInstance;
//lazy Initialiaztion
public static ResourceService getInstance () {
if (resourceInstance == null) { // first check
synchronized(ResourceService.class) {
if (resourceInstance == null) { // double check
// creating instance of ResourceService for only one time
resourceInstance = new ResourceService ();
}
}
}
return resourceInstance;
}
Эта ссылка может вам помочь
http://javarevisited.blogspot.com/2011/06/volatile-keyword-java-example-tutorial.html