пятница, 11 мая 2012 г.

Фабрика потокобезопасных синглтонов

Проблема обычной реализации синглтона состоит в возможности создания множества версий единого экземпляра. В тот момент, когда мы вызываем  getInstance(), экземпляр instance проверяется на null и затем немедленно создается, если instance == null. Проблема в том, что поток А может проверить instance на null и выгрузиться, поток B вызывается, создает Singleton, затем снова вызывается поток A и создает свой Singleton. 

Вот как делается потокобезопасный синглтон: 

public class DBFactory {

    private volatile static DB instance;

    private DBFactory() { }

    public static DB getInstance() {
        if (instance == null) {
            synchronized(this) {
                if (instance == null) {
                    instance = new DB();
                }
            }
        }
        return instance;
    }
}

Вторая проверка if (instance == null) нужна для предотвращения выполнения синхронизации тогда, когда она не требуется и экземпляр обьекта уже существует.

Использование ключевого слова volatile даёт возможность корректно обработать запись и недопустить возникновение следующей проблемы:

  • поток А замечает, что переменная не инициализирована, затем получает блокировку и начинает инициализацию
  • потоку А разрешено присвоить разделяемой переменной ссылку на объект, который находится в процессе инициализации
  • поток Б замечает, что переменная инициализирована (по крайней мере, ему так кажется), и возвращает значение переменной без получения блокировки. Если поток Б теперь будет использовать переменную до того момента, когда поток А закончит инициализацию, поведение программы будет некорректным.
Для обеспечения лучшей читаемости, можно использовать принцип Initialization on demand holder:
public class DBFactory {
    private DBFactory() {}

    private static class DBHolder {
         private static final DB INSTANCE = new DB();
    }

    public static DB getInstance() {
        return DBHolder.INSTANCE;
    }
}

В Java EE все еще проще: можно использовать аннотацию @Singleton
@Singleton
public class DB {}

и использовать @EJB для иньекции:
@EJB
private DB db;
Это способ делегировать работу по созданию экземпляра обьекта контейнеру.



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

Отправить комментарий