懒惰学习
最近,我正在编写log4j附加程序,并希望在自定义附加程序创建过程中使用logger记录一些诊断详细信息,但是log4j初始化仅在创建附加程序实例后才完成,因此在此阶段记录的消息将被忽略。
我感到需要在自定义附加程序中进行延迟初始化,并开始研究选项。 在此博客中,我将分享我尝试过的事情。
我想到的一件事是Singleton方法,但是现在已知的事实是Singleton会导致测试问题,并且无法扩展它,因此混合并发和对象构造的方法并不是那么好。
如果需要单例,那么最好使用依赖注入框架,而不是破坏应用程序代码。 让我们回到延迟初始化/评估。
一些编程语言(例如scala / swift等)支持惰性,因此不需要自定义代码即可执行此操作,但是在Java空间中,我们仍然必须编写线程安全的代码才能正确执行。
让我们看一下Java中的一些选项以及获得的性能类型。
–使用同步的蛮力
这是最简单,效率最低的一种,scala正在使用这种方法。 Scala一个可用@ ScalaLazy.java
public class SingleLock<V> implements Lazy<V> {private Callable<V> codeBlock;private V value;public SingleLock(Callable<V> codeBlock) {this.codeBlock = codeBlock;}@Overridepublic synchronized V get() {if (value == null) {setValue();}return value;}private void setValue() {try {value = codeBlock.call();} catch (Exception e) {throw new RuntimeException(e);}}}
–双锁
编写起来并不复杂,并且具有良好的性能。
public class DoubleLock<V> implements Lazy<V> {private Callable<V> codeBlock;private V value;private volatile boolean loaded;public DoubleLock(Callable<V> codeBlock) {this.codeBlock = codeBlock;}@Overridepublic V get() {if (!loaded) {synchronized (this) {if (!loaded) {setValue();loaded = true;}}}return value;}private void setValue() {try {value = codeBlock.call();} catch (Exception e) {throw new RuntimeException(e);}}}
–使用未来任务
这种方法易于编写,并具有良好的性能。
public class LazyFutureTask<V> implements Lazy<V> {private final FutureTask<V> futureTask;public LazyFutureTask(Callable<V> codeBlock) {this.futureTask = new FutureTask<>(codeBlock);}@Overridepublic V get() {futureTask.run();return getValue();}private V getValue() {try {return futureTask.get();} catch (Exception e) {throw new RuntimeException(e);}}
}
双锁方法可提供最佳性能,而蛮力则是最差的。 我使用不同数量的线程对100万次调用进行了快速基准测试。
单锁性能非常差,让我们通过删除单锁来查看数字,以了解“双锁和未来任务”的执行情况。
这些基准测试很快完成,但是详细的基准测试数字应该接近。 可以在github上获得此博客文章的代码 |
翻译自: https://www.javacodegeeks.com/2016/08/lazy-evaluation.html
懒惰学习