考虑到同步关键字的成本,使延迟初始化线程安全和高效有哪些技巧?



在阅读了 Venkat Subramaniam 的 Page 106-108 - Java 函数式编程一书中昂贵资源的延迟初始化后,发现很难理解这个代码片段的技巧

我的理解: 类Holder中的变量heavy属于Supplier<Heavy>类型

方法创建中的本地类HeavyFactory和缓存重是一个子类扩展供应商

似乎只运行一次来执行该lambda方法,然后更改类Holder.heavy的外部成员变量

我对下面的代码感到困惑,然后重物被分配了新的引用指向子类扩展供应商

请有人可以在这里分享技巧的提示,以获得作者建议的优势,以节省同步关键字的性能损失并注意线程安全。它还提到了虚拟代理模式。我是否错过了任何关键信息来理解它?

package fpij;
import java.util.function.Supplier;
public class Holder {
//only run once here? before heavy get reassigned to HeavyFactory, the local class to that lambda method?
private Supplier<Heavy> heavy = () -> createAndCacheHeavy();
public Holder() {
System.out.println("Holder created");
}
public Heavy getHeavy() {
//the 2nd time it will call always the HeavyFactory.heavyInstance?
return heavy.get();
}

private synchronized Heavy createAndCacheHeavy() {
//create a local class inside method? Is the real trick/hack here I missed out so it will avoid 2nd time the synchronized penalty?
class HeavyFactory implements Supplier<Heavy> {
private final Heavy heavyInstance = new Heavy();
public Heavy get() { return heavyInstance; }
}
if(!HeavyFactory.class.isInstance(heavy)) {
heavy = new HeavyFactory();
}
return heavy.get();
}
public static void main(final String[] args) {
final Holder holder = new Holder();
System.out.println("deferring heavy creation...");
System.out.println(holder.getHeavy());
System.out.println(holder.getHeavy());
}
}

package fpij;
public class Heavy {
public Heavy() { System.out.println("Heavy created"); }
public String toString() { return "quite heavy"; }
}

如果您真的关心同步的成本,有一种简单的方法可以使它正常工作,同时保持初始化延迟。

它使用类装入器确保在装入类时同步的属性。在类完全加载之前,没有其他线程能够访问该类。类加载实际上是惰性的:它仅在第一次实际使用类时才加载类。

如果类 HeavyFactory 的唯一功能是提供单例实例,则只有在调用 getInstance 时才会加载它,并且所有实例都能很好地运行。

class HeavyFactory {
private static final Heavy heavyInstance = initInstance();
public static Heavy getHeavyInstance() {
return heavyInstance;
}
private static Heavy initInstance() {
heavyInstance = new HeavyInstance();
[...] // Other init stuff
return heavyInstance;
}
}

编辑:复杂对象的初始化和依赖关系的连接非常普遍,以至于像JEE或Spring这样的框架已经实现了简化它的方法。

例如,如果您使用 spring,则可以将给定的服务声明为单例,然后在需要的地方声明对它的依赖关系。当 spring 框架以正确的顺序初始化时,将完成 init:

// We instruct spring to create a shared instance of heavy
// with @Component annotation
// The instance would be created eagerly at the start of the app.
@Component
public class Heavy {
public Heavy() { System.out.println("Heavy created"); }
public String toString() { return "quite heavy"; }
}

// For the example another service using the Heavy shared instance
@Component
public class ClientDependingOnHeavy {
// We ask spring to fill it automatically the shared instance for us.
@Autowired
private Heavy heavy; 

public String foo() {
//So we can use the instance like any object. 
System.out.println(heavy.toString());
}
}

井泉或JEE是相当复杂的高级框架。对于一个案例,它们根本不值得。对于整个应用程序,这将是有意义的。如果您还不了解第二个示例,则需要相当多的阅读/教程才能工作。但从长远来看,这可能是值得的。

最新更新