【问题标题】:Impact of multiple threads using same object in a singleton class多个线程在单例类中使用相同对象的影响
【发布时间】:2017-12-11 15:38:41
【问题描述】:

我正在设计一个可以支持不同数据源的模块。 我的模块获取用户的公司 ID 作为输入,我必须根据公司 ID 调用适当的类。 我正在尝试结合一些好的设计并尽可能避免使用条件语句。

我有一个使用这种方法的 FetchDataSource 单例类。

public class FetchDataSourceSingleton {

    private static Map<String, Communicator> communicatorMap;
    public static Communicator getCommunicatorInstance(String dataSourceType) {

        if (communicatorMap == null || communicatorMap.isEmpty())
            populateCommunicatorMap();

        if (communicatorMap.containsKey(dataSourceType))
            return communicatorMap.get(dataSourceType);
        return null; 
    }
.... other methods including populateCommunicatorMap()
}

“Communicator”是一个接口,通信器映射会返回相应的实例。 这是同一个单例类中的 populateCommunicatorMap() 方法。

    private static void populateCommunicatorMap() {

        communicatorMap = new HashMap<String, Communicator>();
        communicatorMap.put("AD", new ADCommunicator());
        communicatorMap.put("DB2", new DB2Communicator());
        communicatorMap.put("MYSQL", new MYSQLCommunicator());
}

ADCommunicator、DB2Communicator 和 MYSQLCommunicator 将实现 Communicator 接口。

该代码似乎在我的测试草稿中有效。 我唯一担心的是 HashMap 将为所有相同类型的通信请求返回相同的对象。如果我想避免条件语句,我似乎无法避免在哈希图中使用相同的实例。否则,我可以像这样调用而不是 hashmap。

Communicator comm;
if (type = "AD") comm = new ADCommunicator();
if (type = "DB2") comm = new DB2Communicator();
if (type = "MYSQL") comm = new MYSQLCommunicator();

我通过使用 hashmap 根据类型返回一个实例来避免这种情况。 但是我无法避免获得相同实例的单例问题。

在需要一次支持数十万个通信请求的多线程环境中,考虑到我需要在每个 Communicator 类中同步大量代码,这可能是个问题。 有没有办法可以避免同步并使其线程安全而不影响性能?

【问题讨论】:

  • 您还可以有一个工厂工厂,其中地图中的值是您的实际 Communicator 实例的工厂。它将被委派给 CommunicatorFactorys 来选择是返回单例 Communicator 还是按需返回新实例,或者介于两者之间。
  • 不要懒惰地填充哈希图,只需在静态块中一劳永逸地填充它(并确保在初始化后永远不会访问它)。那么只要 XxxCommunicator 类是线程安全的,你就可以了。
  • 旁注,if (communicatorMap.containsKey(dataSourceType)) return communicatorMap.get(dataSourceType); return null;return communicatorMap.get(dataSourceType);一模一样。
  • > “我唯一担心的是 HashMap 将为所有相同类型的通信请求返回相同的对象。”我很困惑,您是否想要源的单个实例?
  • @dozer 不,我的意思是一个静态块:static { /* intialise the map */ } 在你的班级。不是静态方法。

标签: java multithreading design-patterns singleton


【解决方案1】:

我似乎无法避免在 hashmap 中有相同的实例

你可以使用一个开关而不是一堆 if。

切换枚举 (Java 5)

type 更改为Java 5+ 中的枚举,然后您可以打开它。为了类型安全,我一般建议使用枚举。

// type is-a enum Communicator.TYPE
switch(type) {
    case AD: return new ADCommunicator();
    case DB2: return new DB2Communicator();
    case MYSQL: return new MYSQLCommunicator();
    default: return null;
}

切换字符串 (Java 8)

Java 8 可以直接切换字符串。

// type is-a String
switch(type) {
    case "AD": return new ADCommunicator();
    case "DB2": return new DB2Communicator();
    case "MYSQL": return new MYSQLCommunicator();
    default: return null;
}

切换枚举将与地图一样快,如果不是更快的话。打开字符串会像地图一样快。

工厂地图(工厂的工厂)

或者有工厂地图:

private final static Map<String, Factory<? extends Communicator>> map;
static {
    map.put("AD", ADCommunicatorFactory.getInstance());
    //...
    map.put(null, NullFactory<Communicator>.getInstance());
} // populated on class-load. Eliminates race from lazy init

// on get
return map.get(type).make();

类映射(反射)

或者使用反射 API 来创建实例,但是使用条件可能会更好。

// on init
Map<String, Class<? extends Communicator>> map = new HashMap<>();
map.put("AD", ADCommunicator.class);

// on get
try {
    return (Communicator) map.get(type).newInstance();
} catch(InstantiationException | IllegalAccessException | NullPointerException e) {
    return null;
}

附: 这听起来像是过早的优化。我怀疑确定使用哪个 Communicator 会成为您系统的瓶颈。

【讨论】:

  • 谢谢。最后一点看起来很有趣,但我得到了这个错误。 Map> 类型中的 put(String, Class) 方法不适用于每个 put 调用的参数 (String, Class)。我究竟做错了什么?没关系 - 我应该使用 Map>。不过感谢您的想法。
  • @Dozer 已修复。抱歉,我没有要编译的类。
【解决方案2】:

如果您的所有通信器都可以使用空参数列表构造函数构造,那么您可以将通信器的类型(类)存储在映射中而不是实例中。然后您可以从 communicatorMap 中查找类型 (java.lang.Class) 并使用 java.lang.Class.newInstance() 实例化一个新实例。

例如:

public interface Communicator {
    void communicate();
}

public class Communicator1 implements Communicator {
    public void communicate() {
        System.out.println("communicator1 communicates");
    }
}

public class Communicator2 implements Communicator {
    public void communicate() {
        System.out.println("communicator2 communicates");
    }
}

public class CommuniicatorTest {
    public static void main(String[] args) throws Exception {
        Map<String, Class<? extends Communicator>> communicators = new HashMap<String, Class<? extends Communicator>>();
        communicators.put("Comm1", Communicator1.class);
        communicators.put("Comm2", Communicator2.class);
        Communicator comm2 = communicators.get("Comm2").newInstance();
        comm2.communicate();
        System.out.println("comm2: " + comm2);
        Communicator anotherComm2 = communicators.get("Comm2").newInstance();
        anotherComm2.communicate();
        System.out.println("anotherComm2: " + anotherComm2);
    }
}

结果:

communicator2 communicates
comm2: pack.Communicator2@6bc7c054
communicator2 communicates
anotherComm2: pack.Communicator2@232204a1

【讨论】:

  • 太棒了。我会试试这个。谢谢!
【解决方案3】:

Assylias 关于使用静态初始化器是正确的。它在您的类加载时运行,这保证了地图将在类发生其他任何事情之前加载。

您没有显示地图的声明;我假设它是静态的。

private final static Map<String, Communicator> communicatorMap;
static {
    communicatorMap = new HashMap<>();
    communicatorMap.put("AD", new ADCommunicator());
    communicatorMap.put("DB2", new DB2Communicator());
    communicatorMap.put("MYSQL", new MYSQLCommunicator());
}; // populated on class-load. Eliminates race from lazy init

剩下的问题是 Communicator 的实现。所有这些都假设它也是线程安全的。

【讨论】:

    猜你喜欢
    • 2023-03-18
    • 1970-01-01
    • 2020-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多