Вы можете добавить зависимый экземпляр, переопределить getInstance () из кода модульного теста, использовать аспектно-ориентированное программирование, чтобы перехватить вызов метода и вернуть другой объект, или использовать такой инструмент, как jmockit , который позволяет вам издеваться над чем угодно, включая статики, финальные классы, конструкторы и все, что люди обычно говорят, что это «не проверяется».
Один из подходов, которые я использовал в унаследованных системах (где я хотел сделать что-то тестируемое с минимальным влиянием на архитектуру системы), заключался в изменении фабричных методов (getInstance), чтобы проверить системное свойство для альтернативной реализации, которую я хотел бы вместо этого создать экземпляр. Это был альтернативный фиктивный объект в наборе модульных тестов.
Что касается оператора "двойная проверка блокировки сломана", то это больше не так, если вы используете ключевое слово volatile и Java> = 1.5. Он был сломан (даже с volatile) с 1.4 и более ранними версиями, но если вы знаете, что ваш код будет работать только на последних JVM, я бы не стал беспокоиться об этом. Но в любом случае я бы также не использовал синглтон: наличие контейнера DI / IOC для управления жизненным циклом объекта решило бы обе ваши проблемы (тестируемость и узкое место синхронизированного доступа) гораздо более элегантно.