У меня есть вопрос о том, как работает загрузка классов / сборка мусора в Android. Мы несколько раз сталкивались с этой проблемой, и, насколько я могу судить, Android ведет себя здесь не так, как обычная JVM.
Проблема заключается в следующем: в настоящее время мы пытаемся сократить одноэлементные классы в приложении в пользу единственного корневого фабричного синглтона, единственной целью которого является управление другими классами менеджера. Менеджер высшего уровня, если хотите. Это облегчает нам замену реализаций в тестах без выбора полного решения DI, поскольку все действия и службы имеют одинаковые ссылки на эту корневую фабрику.
Вот как это выглядит:
public class RootFactory {
private static volatile RootFactory instance;
@SuppressWarnings("unused")
private Context context; // I'd like to keep this for now
private volatile LanguageSupport languageSupport;
private volatile Preferences preferences;
private volatile LoginManager loginManager;
private volatile TaskManager taskManager;
private volatile PositionProvider positionManager;
private volatile SimpleDataStorage simpleDataStorage;
public static RootFactory initialize(Context context) {
instance = new RootFactory(context);
return instance;
}
private RootFactory(Context context) {
this.context = context;
}
public static RootFactory getInstance() {
return instance;
}
public LanguageSupport getLanguageSupport() {
return languageSupport;
}
public void setLanguageSupport(LanguageSupport languageSupport) {
this.languageSupport = languageSupport;
}
// ...
}
initialize
вызывается один раз, в Application.onCreate
, т. Е. до запускается любое действие или служба. Теперь возникает проблема: метод getInstance
иногда возвращается как null
- даже если он вызывается в том же потоке! Похоже, это не проблема видимости; вместо этого сборщик статических одноэлементных ссылок на уровне класса, кажется, на самом деле был очищен сборщиком мусора. Возможно, я делаю поспешные выводы, но это может быть потому, что сборщик мусора Android или механизм загрузки классов могут на самом деле выгружать классы, когда памяти становится мало, и в этом случае исчезнет единственная ссылка на экземпляр singleton ? Я не очень глубоко разбираюсь в модели памяти Java, но, полагаю, этого не должно произойти, иначе этот обычный способ реализации синглетонов не сработает на любой JVM, верно?
Есть идеи, почему именно это происходит?
PS: можно обойти это, сохранив вместо этого «глобальные» ссылки на один экземпляр приложения. Это оказалось надежным, когда нужно держать объект в течение всего срока службы приложения.
UPDATE
Очевидно, мое использование летучих здесь вызвало некоторую путаницу. Мое намерение состояло в том, чтобы гарантировать, что текущее состояние статической ссылки всегда видимо для всех потоков, обращающихся к ней. Я должен сделать это, потому что я пишу и читаю эту ссылку из более чем одного потока: в обычном приложении, запускаемом только в основном потоке приложения, но в тестовом прогоне инструментария, где объекты заменяются на mocks, я пишу его из Поток инструментовки и читать его в потоке пользовательского интерфейса. Я мог бы также синхронизировать вызов с getInstance
, но это дороже, так как требует блокировки объекта. См. Каков эффективный способ реализации одноэлементного шаблона в Java? для более подробного обсуждения этого вопроса.