Недавно я писал log4j appender и хотел использовать в нем logger для регистрации некоторых диагностических подробностей во время создания пользовательского appender, но инициализация log4j завершается только после создания экземпляра appender, поэтому сообщения, зарегистрированные на этом этапе, игнорируются.
Я почувствовал необходимость ленивой инициализации в пользовательском приложении и начал искать варианты. В этом блоге я поделюсь вещами, которые я попробовал.
Одна вещь, которая пришла мне в голову, была синглтонным подходом, но теперь известен тот факт, что синглтон вызывает проблемы с тестированием и делает невозможным его расширение, поэтому подход смешивания параллелизма и построения объекта не так уж хорош.
Если требуется синглтон, лучше использовать платформу Dependency Injection, а не портить код приложения. Вернемся к ленивой инициализации / eval.
В некоторых языках программирования, таких как scala / swift и т. Д., Есть поддержка lazy, поэтому для этого не требуется никакого специального кода, но в Java-пространстве нам все равно приходится писать потокобезопасный код, чтобы сделать его правильным.
Давайте посмотрим на некоторые варианты, которые мы имеем в Java и какой тип производительности мы получаем.
— Перебор с использованием Синхронизированного
Это самый простой и неэффективный подход, который использует Scala. Scala один доступен @ ScalaLazy.java
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | publicclassSingleLock<V> implementsLazy<V> {    privateCallable<V> codeBlock;    privateV value;    publicSingleLock(Callable<V> codeBlock) {        this.codeBlock = codeBlock;    }    @Override    publicsynchronizedV get() {        if(value == null) {            setValue();        }        returnvalue;    }    privatevoidsetValue() {        try{            value = codeBlock.call();        } catch(Exception e) {            thrownewRuntimeException(e);        }    }} | 
— Двойной замок
Это немного сложно написать и дает хорошую производительность.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | publicclassDoubleLock<V> implementsLazy<V> {    privateCallable<V> codeBlock;    privateV value;    privatevolatilebooleanloaded;    publicDoubleLock(Callable<V> codeBlock) {        this.codeBlock = codeBlock;    }    @Override    publicV get() {        if(!loaded) {            synchronized(this) {                if(!loaded) {                    setValue();                    loaded = true;                }            }        }        returnvalue;    }    privatevoidsetValue() {        try{            value = codeBlock.call();        } catch(Exception e) {            thrownewRuntimeException(e);        }    }} | 
— Используя задачу Future
Этот подход прост в написании и дает хорошую производительность.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | publicclassLazyFutureTask<V> implementsLazy<V> {    privatefinalFutureTask<V> futureTask;    publicLazyFutureTask(Callable<V> codeBlock) {        this.futureTask = newFutureTask<>(codeBlock);    }    @Override    publicV get() {        futureTask.run();        returngetValue();    }    privateV getValue() {        try{            returnfutureTask.get();        } catch(Exception e) {            thrownewRuntimeException(e);        }    }} | 
Подход с двойным замком дает лучшую производительность, а грубая сила — худшая Я сделал быстрый тест для 1 миллиона вызовов, используя другое количество потоков.
|   | 
| Производительность одиночной блокировки очень плохая, давайте посмотрим на число, сняв одиночную блокировку, чтобы увидеть, как выполнялась двойная блокировка и будущая задача. Эти тесты выполняются очень быстро, но подробные номера тестов должны быть близки. Код для этого блога доступен @ github | 
| Ссылка: | Ленивая оценка от нашего партнера JCG Ашкрит Шарма в блоге « Готовы ли вы» . | 
