考虑到sychronized关键字的成本,有什么技巧可以让懒惰初始化线程安全高效?

问题描述 投票:0回答:1

看了Venkat Subramaniam所著的《Java中的函数式编程》第106-108页左右的书中对昂贵资源的懒惰初始化,发现用这个代码段很难理解其中的技巧。

我的理解:变量 heavy 课堂上 Holder 属于 Supplier<Heavy>

地方课 HeavyFactory 在方法createAndCacheHeavy里面有一个子类extends Supplier。

似乎只运行一次就能执行那个lambda方法,然后改变class Holder.havy的外部成员变量。

我对下面的代码感到困惑的是,重物随后被赋予了新的指向子类extends Supplier的引用。

请谁能在这里分享一下技巧,以获得作者提出的优点,以节省同步关键字的性能惩罚,并照顾到线程安全。其中还提到了虚拟代理模式。我是不是漏掉了什么关键信息才明白?

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"; }
}
java functional-programming thread-safety lazy-initialization
1个回答
0
投票

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

它使用的属性是当类被加载时,classloader确保同步。它保证在类被完全加载之前,没有其他线程能够访问该类。而类的加载实际上是懒惰的:它只在第一次真正使用类时才加载类。

如果类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;
  }
}

编辑:复杂对象的init和依赖关系的布线非常普遍,所以JEE或Spring等框架都实现了做简化的方法。

比如说,如果你使用spring,你就可以直接把一个给定的服务声明为单例,然后在需要的地方声明一个依赖关系。init将在spring框架初始化时以适当的顺序完成。

// 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是相当复杂的高级框架。对于单个案例来说,它们根本不值得。对于整个应用程序来说,这是有意义的。如果你还不知道的话,你需要阅读一些教程来使第二个例子发挥作用。但从长远来看,这可能是值得的。

© www.soinside.com 2019 - 2024. All rights reserved.