1

(也许是“如何在 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;
    }
}

有一个更好的习惯用法来让每个类型参数值都具有一个单例吗?

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

4

2 回答 2

1

请不要那样做。
A. 你的单例不是线程安全的。
B. 注意Java 的双重检查模式问题
C. 在每个类中都有静态初始化程序并拥有:

static {
   instance = new MySingleton();
}

进而

public static MySingleton getInstance() {
return instance
}

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

public Object getInstanceByType(Class<?> clazz)

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

于 2012-11-19T10:23:59.127 回答
1

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

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<?>>());

或者用 aConcurrentHashMap代替。

于 2012-11-19T10:28:02.940 回答