首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >考虑到sychronized关键字的开销,让惰性初始化线程安全高效的诀窍是什么?

考虑到sychronized关键字的开销,让惰性初始化线程安全高效的诀窍是什么?
EN

Stack Overflow用户
提问于 2020-05-31 17:46:33
回答 1查看 127关注 0票数 0

在阅读Venkat Subramaniam在第106-108页- Java中的函数式编程一书中对昂贵资源的延迟初始化后,您会发现很难理解此代码片段的诀窍

我的理解是:Holder类中的变量heavySupplier<Heavy>类型

vs

方法createAndCacheHeavy内的局部类HeavyFactory是扩展Supplier的子类

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

我对下面的代码感到困惑,然后将heavy赋值为指向子类extends的新引用

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

代码语言:javascript
复制
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"; }
}
EN

回答 1

Stack Overflow用户

发布于 2020-05-31 19:34:37

如果你真的关心同步的成本,有一个简单的方法可以让它正常工作,同时保持初始化懒惰。

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

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

代码语言:javascript
复制
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框架按正确的顺序初始化时完成:

代码语言:javascript
复制
// 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());
  }
}

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62114335

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档