问题描述
|
我需要在并发环境中延迟加载资源。加载资源的代码应仅执行一次。
经过双重检查的锁定(使用JRE 5+和volatile关键字)和按需持有人初始化习惯似乎都非常适合。
仅通过查看代码,按需持有人的惯用语初始化似乎更干净,更高效(但是,我猜这里)。尽管如此,我仍要小心并记录每一个Singleton中的模式。至少对我来说,很难理解为什么当场写这样的代码...
我的问题是:哪种方法更好?又为什么呢
如果您的答案为否。您将如何在Java SE环境中解决此要求?
备择方案
我可以为此使用CDI而不在整个项目中使用它吗?有文章吗?
解决方法
添加另一个也许更清洁的选项。我建议枚举变量:
在Java中将枚举用作单例的最佳方法是什么?
, 就可读性而言,我将按需进行初始化。我认为,经过双重检查的锁定是过时且丑陋的实现。
从技术上讲,通过选择双重检查锁定,您总是会在该字段上产生易失性读取,因为您可以使用按需初始化持有人惯用语来进行常规读取。
, 按需初始化持有人仅适用于单例,您不能按实例延迟加载元素。双重检查锁定给必须看课的每个人增加了认知负担,因为很容易以微妙的方式犯错。在将模式封装到并发库中的实用程序类之前,我们曾经为此遇到过各种麻烦
我们有以下选择:
Supplier<ExpensiveThing> t1 = new LazyReference<ExpensiveThing>() {
protected ExpensiveThing create() {
… // expensive initialisation
}
};
Supplier<ExpensiveThing> t2 = Lazy.supplier(new Supplier<ExpensiveThing>() {
public ExpensiveThing get() {
… // expensive initialisation
}
});
就用法而言,两者都有相同的语义。第二种形式使内部供应商使用的所有引用在初始化后都可用于GC。第二种形式还支持使用TTL / TTI策略的超时。
, 按需初始化持有人始终是实现单例模式的最佳实践。它很好地利用了JVM的以下功能。
静态嵌套类仅在按名称调用时才加载。
默认情况下,类加载机制是并发保护的。因此,当一个线程初始化一个类时,其他线程将等待其完成。
另外,您不必使用syncnize关键字,它会使您的程序慢100倍。
, 我怀疑按需持有人的初始化要比经过双重检查的锁定(使用volatile)快一些。原因是前者一旦创建实例便没有同步开销,但后者涉及读取volatile(我认为)需要读取完整的内存。
如果性能不是很重要的问题,那么同步“ 1”方法是最简单的。