【问题标题】:Best idiom for creating a separate singleton for every type argument to a generic class?为泛型类的每个类型参数创建单独的单例的最佳习惯用法?
【发布时间】:2012-11-07 05:20:15
【问题描述】:

(也许是"How to create a generic singleton class in java?"的补充问题:)

class MyClass<T> {
    private static Map<Class<MyClass<?>>, MyClass<?>> s_instances =
        new HashMap<Class<MyClass<?>>, MyClass<?>>();

    public static MyClass<?> blah(Class<MyClass<?>> clz)
            throws InstantiationException, IllegalAccessException {
        if (s_instances.get(clz) != null)
            return s_instances.get(clz);
        MyClass<?> instance = clz.newInstance();
        s_instances.put(clz, instance);
        return instance;
    }
}

有没有更好的习惯用法来表示每个类型参数值都只有一个单例?

编辑:请不要仅仅为了指出缺乏线程安全而回答。取点。我在问我是否可以做一些比这张地图更优雅的事情。

【问题讨论】:

  • 在您的示例中,不太确定每种类型都有单独的实例有什么意义。新实例的类型必须与 MyClass 完全相同(不是子类),因此您可以调用 MyClass 的构造函数(无需反射)。

标签: java generics static singleton idioms


【解决方案1】:

请不要那样做。
A. 你的单例不是线程安全的。
B. 注意double check pattern issues at Java
C. 每个类中都有静态初始化器真的那么难吗:

static {
   instance = new MySingleton();
}

然后

public static MySingleton getInstance() {
return instance
}

如果你真的坚持 -
1.您可能可以定义一个单例,它将管理映射到实例中的类型(键是类或完整的类名,值是对象)
2.您可以在那里添加注册您想要的类型(我建议他们有私人CTOR)。
3. 使用this 答案以调用私有 CTOR ,并创建一个实例以放置在映射条目的值中。
4. 为1中提到的repository提供一个getInstance方法,签名为:

public Object getInstanceByType(Class<?> clazz)

此方法将从内部映射中获取实例。

【讨论】:

  • 关于线程安全的观点(尽管对于我的具体用法来说这不是问题)。你为什么不建议我这样做?
  • 因为这意味着向编码器添加一个几乎没有任何实际价值的基础设施。每个类可以有单例,每个类 6 行代码 - 这值得添加基础架构吗?
  • 我没跟... 不知道type参数的值是什么,所以不能加上那6行代码。
【解决方案2】:

你的方法不是线程安全的:

private static Map<Class<MyClass<?>>, MyClass<?>> s_instances =
    new HashMap<Class<MyClass<?>>, MyClass<?>>();

public static MyClass<?> blah(Class<MyClass<?>> clz)
        throws InstantiationException, IllegalAccessException {
    if (s_instances.get(clz) != null)
        return s_instances.get(clz);
    // here1
    MyClass<?> instance = clz.newInstance();
    s_instances.put(clz, instance);
    // here2
    return instance;
}

一旦一个线程越过标记为//here1 的行,第二个线程可能会在第一个线程到达标记为//here2 的行之前进入该方法,因此创建第二个相同类型的“单例”并覆盖第一个在地图中。

快速解决方法是在地图上同步:

public static MyClass<?> blah(Class<MyClass<?>> clz)
        throws InstantiationException, IllegalAccessException {
  synchronized(s_instances){
    if (s_instances.get(clz) != null)
        return s_instances.get(clz);
    // here1
    MyClass<?> instance = clz.newInstance();
    s_instances.put(clz, instance);
    // here2
    return instance;
  }
}

但是,这意味着许多线程将不得不等待很多时间,最终可能会杀死您的应用程序。可能你应该做的是一个两步解决方案:

public static MyClass<?> blah(Class<MyClass<?>> clz)
        throws InstantiationException, IllegalAccessException {
  Object candidate = s_instances.get(clz);
  if(clz.isInstance(candidate)){ // implicit null check
      return clz.cast(candidate);
  }
  synchronized(s_instances){
    Object candidate = s_instances.get(clz);
    if(clz.isInstance(candidate)){  // gotta check a second time in a
        return clz.cast(candidate); // synchronized context
    }
    MyClass<?> instance = clz.newInstance();
    s_instances.put(clz, instance);
    return instance;
  }
}

另外,HashMap 不适合并发访问,所以你应该把它包装在Collections.synchronizedMap()

private static Map<Class<MyClass<?>>, MyClass<?>> s_instances =
    Collections.synchronizedMap(new HashMap<Class<MyClass<?>>, MyClass<?>>());

或者改用ConcurrentHashMap

【讨论】:

  • 这就是为什么我建议在我的回答中预先注册类型。他总是可以尝试通过使用预定义的映射来减少锁定时间,比如说 N 个条目到内部映射。它并不完全像 ConcurrentMap,因为他将管理“段”并在应用程序启动时创建所有这些段,因此您无需锁定添加新段。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多