Какие проблемы возникают при реализации Singleton с изменяемым состоянием
Что проверяют
Понимаете ли вы, что синглтон с изменяемым состоянием это глобальная переменная под другим названием. Следом обычно спрашивают про Spring: бины там по умолчанию синглтоны, и что будет, если сохранить в поле сервиса данные запроса.
Как отвечать
Разделите две проблемы. Создание экземпляра решено давно: enum с одним элементом или ленивый holder-класс, оба потокобезопасны без явных блокировок. А состояние внутри остаётся общим для всех:
- гонки: несколько потоков меняют поля одновременно,
HashMapв поле портится, составные операции теряют обновления. НужныConcurrentHashMap, атомики или замена целого неизменяемого снимка черезAtomicReference; - утечка между пользователями: самая частая авария в Spring.
@Serviceхранит в поле текущего пользователя или результат последнего запроса, и под нагрузкой один клиент видит данные другого. Данные запроса передают параметрами или держат в бине со scoperequest; - тесты: состояние переживает тест, результат зависит от порядка запуска, а при параллельном прогоне в JUnit 5 тесты падают случайно. Приходится заводить
reset()только ради тестов; - скрытые зависимости: вызов
Config.getInstance()внутри метода не виден в сигнатуре, подменить его заглушкой без внедрения зависимостей трудно; - «один» только внутри JVM и загрузчика классов: при трёх репликах сервиса у вас три разных состояния, и кэш или счётчик в синглтоне расходятся между подами.
Итог для ответа: синглтон должен быть без состояния или с неизменяемым состоянием, например с конфигурацией, прочитанной при старте. Изменяемые данные живут в базе, в Redis или в объектах с коротким жизненным циклом.
Где спотыкаются
Отвечают только про double-checked locking. Это про создание экземпляра, а вопрос про состояние, которое ломается и в идеально созданном синглтоне.
Предлагают ThreadLocal как универсальное лекарство. В пуле потоков значение переживает запрос и достаётся следующему, если не вызвать remove() в finally.
Делают все методы synchronized. Гонок нет, но сервис превращается в узкое место, через которое запросы проходят по одному.