懒惰学习
最近,我正在编写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;
}
@Override
public 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;
}
@Override
public 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);
}
@Override
public 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
懒惰学习