使用volatile关键字创建一个实例

时间:2017-04-14 13:56:08

标签: java concurrency osgi

我有以下代码:

public class EntityManagerFactoryProviderImpl implements EntityManagerFactoryProvider {

  private EntityManagerFactory entityManagerFactory=null;//line XXX

  public EntityManagerFactory getFactory(){
      if (entityManagerFactory==null){
          buildFactory();
      }
      return entityManagerFactory;
  }

  private synchronized void buildFactory(){
      if (entityManagerFactory!=null){
          return;
      }
      entityManagerFactory=...
  }
}

所以我需要entityManagerFactory实例只创建一次 - 首次调用getFactory()时。

我必须在第XXX行设置变量entityManagerFactory作为此情况的易变?

此外,EntityManagerFactoryProviderImpl是OSGI Singleton声明服务,因此该类始终只有一个实例。

3 个答案:

答案 0 :(得分:2)

有多个线程并行调用代码的理论可能性;并且由于使用volatile,线程A没有看到线程B所做的更新。我自己从未遇到过这样的行为,但是:它是可能的,当它发生时,非常奇怪的错误可能有两个相同的单身实例。

您可以研究此SEI cert site,以便对该主题进行全面讨论。

答案 1 :(得分:1)

我不明白为什么你需要让这个变化。您在示例中使用双重检查锁定解决方案。接受的答案中的link也表明这符合要求。

所以接受的答案实际上是错误的,你不需要挥发性的。但是,这种初始化最干净的解决方案是“初始化按需持有者类成语”,它也在link

更新我错了。双检查锁定可能会失败,因为可以在部分构造的状态中看到EntityManagerFactory对象,只保证可以看到此对象中的最终字段。这在http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5-110

中明确提到
  int j = f.y;  // could see 0

“初始化按需持有人类成语”是最快的。

答案 2 :(得分:0)

有问题的代码不是线程安全的,因为它在公共方法中有一个检查然后处理问题。这个问题的原因是共享的可变状态在类中没有正确地同步。这是一个解决方案:

public class EntityManagerFactory implements ....{

private static class EntityManagerHolder {
  public static EntityManager entityManager = new EntityManager();
}

public static EntityManager getEntityManager(){
  return EntityManagerHolder.entityManager;
}

}

请注意,此处没有synchronized关键字。这是因为entityManager在holder类中被初始化为static,这意味着它在类加载之后但在任何线程可以启动之前加载,因此不需要同步化。

holder类的唯一目的是通过使用JVM延迟类加载策略来防止急切的初始化成本。 holder类仅在实际需要时初始化,而不是在类加载时进行初始化。

Bottomline:你可以避免同步和急切的初始化,并一举获得一个单身。 ;)