我应该将Jackson的ObjectMapper声明为静态字段吗?


361

杰克逊图书馆的ObjectMapper班级似乎是线程安全的

这是否意味着我应该ObjectMapper像这样将我声明为静态字段?

class Me {
    private static final ObjectMapper mapper = new ObjectMapper();
}

而不是像这样的实例级字段?

class Me {
    private final ObjectMapper mapper = new ObjectMapper();
}

Answers:


505

是的,建议您这样做是安全的。

您所引用页面的唯一警告是,一旦共享了映射器,您将无法修改它的配置。但是您不更改配置,这样就可以了。如果确实需要更改配置,则可以从静态块执行此操作,也可以。

编辑:(2013/10)

在2.0及更高版本中,可以通过注意到还有一个更好的方法来增强上述功能:使用ObjectWriterObjectReader对象,可以通过构造ObjectMapper。它们是完全不变的,线程安全的,这意味着从理论上讲甚至不可能引起线程安全问题(ObjectMapper如果代码尝试重新配置实例,则可能发生此问题)。


23
@StaxMan:我有点担心ObjectMapperObjectMapper#setDateFormat()调用之后是否仍然是线程安全的。众所周知,这SimpleDateFormat不是线程安全的,因此ObjectMapper除非它例如SerializationConfig在每个线程之前克隆writeValue()(我怀疑),否则不会如此。你能揭穿我的恐惧吗?
dma_k 2013年

49
DateFormat确实被克隆了。那里很可疑,但是你被遮住了。:)
StaxMan

3
在大型企业应用程序的单元/集成测试期间,我遇到了奇怪的行为。当将ObjectMapper设置为静态最终类属性时,我开始面临PermGen问题。有人愿意解释可能的原因吗?我正在使用2.4.1版的jackson-databind。
Alejo Ceballos 2015年

2
@MiklosKrivan您看过ObjectMapper吗?方法被命名为writer()reader()(和一些readerFor()writerFor())。
StaxMan'5

2
没有mapper.with()调用(因为Jackson中的“ with”表示构造新实例和线程安全执行)。但是关于配置更改:不进行检查,因此ObjectMapper必须保护对配置的访问。至于“ copy()”:是的,它会创建一个新的新副本,该副本可以根据相同的规则进行完全(重新)配置:先完全配置,然后再使用,这样就可以了。有不平凡的成本相关联(因为副本不能使用任何缓存的处理程序),但这是安全的方法,是的。
StaxMan

53

尽管ObjectMapper是线程安全的,但我强烈建议不要将其声明为静态变量,尤其是在多线程应用程序中。甚至不是因为这是一种不好的做法,而是因为您面临着严重的死锁风险。我是根据自己的经验讲的。我创建了一个具有4个相同线程的应用程序,这些线程正在从Web服务获取和处理JSON数据。根据线程转储,我的应用程序经常停在以下命令上:

Map aPage = mapper.readValue(reader, Map.class);

除此之外,性能也不佳。当我将静态变量替换为基于实例的变量时,停滞消失了,性能提高了三倍。也就是说,在40分钟56秒内处理了240万个JSON文档,而不是之前的2.5个小时。


15
加里的答案完全有道理。但是,ObjectMapper为每个类实例创建一个实例可以防止锁定,但是稍后可能会对GC造成很大的负担(请为您创建的类的每个实例想象一个ObjectMapper实例)。可以采用一种中间路径方法,而不是在ObjectMapper整个应用程序中仅保留一个(公共)静态实例,而可以在每个类中声明一个(私有)静态实例。这将减少全局锁定(通过按类分配负载),也不会创建任何新对象,因此也可以打开GC。ObjectMapper
阿比德蒙

当然,保持一个 ObjectPool 是你可以用最好的方式-从而提供最佳GCLock表演。您可以参考以下链接以了解apache-common的ObjectPool实现。 commons.apache.org/proper/commons-pool/api-1.6/org/apache/...
Abhidemon

16
我建议一种替代方案:保持静态的ObjectMapper地方,但只得到ObjectReader/ ObjectWriter实例(通过辅助方法),保留那些在其他地方引用(或动态调用)。这些读取器/写入器对象不仅是完全线程安全的wrt重新配置,而且非常轻量(wrt映射器实例)。因此,保留数千个引用不会增加太多内存使用。
StaxMan

因此,对ObjectReader实例的调用是否不会阻塞,即说在多线程应用程序中调用了ObjectReader.readTree,使用杰克逊2.8.x
Xephonia,

1

尽管就线程安全而言,声明静态ObjectMapper是安全的,但是您应该意识到,在Java中构造静态Object变量被认为是不好的做法。有关更多详细信息,请参见为什么将静态变量视为邪恶?(如果您愿意,我的回答

简而言之,应避免使用静态方法,因为这样会使编写简洁的单元测试变得困难。例如,对于静态最终ObjectMapper,您无法将JSON序列化换成虚拟代码或无操作。

另外,静态的final阻止您在运行时重新配置ObjectMapper。您可能现在没有想到这样做的原因,但是如果您将自己锁定在静态的最终模式中,那么只需拆除类加载器,就可以重新初始化它。

在使用ObjectMapper的情况下,它很好,但是通常是不好的做法,与使用单例模式或控制反转来管理寿命长的对象相比,它没有任何优势。


27
我建议,尽管静态STATEFUL单例通常是一个危险信号,但在这种情况下,共享一个(或少量)实例是有充分理由的。可能要使用依赖注入。但同时值得一问的是要解决的是实际问题还是潜在问题。这尤其适用于测试:仅在某些情况下可能存在问题并不意味着它适合您的使用。所以:意识到问题,太好了。假设“一种尺寸适合所有人”,则不是很好。
StaxMan

3
显然,了解任何设计决策所涉及的问题都很重要,并且如果您可以做一些事情而不会导致用例出现问题,那么根据定义,您不会造成任何问题。但是,我认为使用静态实例没有任何好处,并且随着您的代码的发展或将其移交给可能不了解您的设计决策的其他开发人员,它在将来为重大麻烦打开了大门。如果您的框架支持替代方案,那么没有理由不避免使用静态实例,那么它们肯定没有优势。
JBCP

11
我认为该讨论涉及非常笼统且不太有用的切线。我毫不怀疑建议对静态单例感到怀疑。我只是对这种特殊情况的用法非常熟悉,并且我认为不能从一组一般准则中得出具体结论。因此,我将保留它。
StaxMan

1
最新评论,但是ObjectMapper会特别反对这个想法吗?它暴露readerForwriterFor其创建ObjectReaderObjectWriter按需实例。因此,我想将具有初始配置的映射器放置在静态某处,然后根据需要使用按情况配置的读取器/写入器?
卡里根(Carighan)

1

如果您不想将其定义为静态最终变量,但想节省一些开销并确保线程安全,则可以从此PR中了解到一个技巧。

private static final ThreadLocal<ObjectMapper> om = new ThreadLocal<ObjectMapper>() {
    @Override
    protected ObjectMapper initialValue() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return objectMapper;
    }
};

public static ObjectMapper getObjectMapper() {
    return om.get();
}

归功于作者。


2
但是存在内存泄漏的风险,因为ObjectMapper将被附加到可能是池一部分的线程上。
肯斯顿·崔

@KenstonChoi AFAIU,应该没问题。线程来去去去,线程局部变量随线程一起来去。根据并发线程的数量,您可能会或可能不会负担内存,但是我看不到“泄漏”。
伊万·巴拉索夫

2
@IvanBalashov,但是如果线程是在线程池中创建/从线程池返回的(例如,像Tomcat这样的容器),它将保留。在某些情况下可能需要这样做,但是我们需要注意一些事情。
肯斯顿·崔

-1

com.fasterxml.jackson.databind.type.TypeFactory._hashMapSuperInterfaceChain(HierarchicType)

com.fasterxml.jackson.databind.type.TypeFactory._findSuperInterfaceChain(Type, Class)
  com.fasterxml.jackson.databind.type.TypeFactory._findSuperTypeChain(Class, Class)
     com.fasterxml.jackson.databind.type.TypeFactory.findTypeParameters(Class, Class, TypeBindings)
        com.fasterxml.jackson.databind.type.TypeFactory.findTypeParameters(JavaType, Class)
           com.fasterxml.jackson.databind.type.TypeFactory._fromParamType(ParameterizedType, TypeBindings)
              com.fasterxml.jackson.databind.type.TypeFactory._constructType(Type, TypeBindings)
                 com.fasterxml.jackson.databind.type.TypeFactory.constructType(TypeReference)
                    com.fasterxml.jackson.databind.ObjectMapper.convertValue(Object, TypeReference)

com.fasterxml.jackson.databind.type.TypeFactory类中的_hashMapSuperInterfaceChain方法已同步。在高负载下看到相同的竞争。

避免静态ObjectMapper的另一个原因


1
确保检查出最新版本(也许在这里指出您使用的版本)。已基于报告的问题对锁定进行了改进,并且为Jackson 2.7完全重写了类型解析(f.ex)。尽管在这种情况下,TypeReference使用起来有点昂贵:如果可能的话,将其解决JavaType将避免大量处理(TypeReference不幸的是,由于我不会在这里介绍的原因,无法对其进行缓存),因为它们是“完全解析的”(超级类型,通用类型等)。
StaxMan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.