杰克逊库中的ObjectMapper
类似乎是线程安全的。
这是否意味着我应该像这样声明我的ObjectMapper
为静态字段
class Me {
private static final ObjectMapper mapper = new ObjectMapper();
}
那么,与实例级字段不同,应该怎样做呢?
class Me {
private final ObjectMapper mapper = new ObjectMapper();
}
是的,这是安全的且推荐的。
从您提供的页面中唯一需要注意的是,一旦共享了Mapper的配置,则无法修改其配置;但是您没有更改配置,所以这很好。如果您确实需要更改配置,则可以在静态块中执行该操作,也会很好。
编辑: (2013/10)
从2.0及以上版本开始,还有一种更好的方法:使用ObjectMapper
构建的ObjectWriter
和ObjectReader
对象。它们完全不可变且线程安全,这意味着不可能出现线程安全问题(如果代码尝试重新配置实例,则会出现此类问题)。
虽然ObjectMapper是线程安全的,但我强烈反对将其声明为静态变量,特别是在多线程应用程序中。 这不仅是因为这是一种不良实践,而且因为您正在承担死锁的巨大风险。我从自己的经验中得知。我创建了一个应用程序,其中有4个相同的线程从Web服务获取和处理JSON数据。 根据线程转储,我的应用程序经常在以下命令上停顿:
Map aPage = mapper.readValue(reader, Map.class);
此外,性能不佳。当我将静态变量替换为基于实例的变量时,停顿消失了,并且性能提高了四倍。即,处理了 240 万个 JSON 文档仅用 40 分钟 56 秒,而之前需要 2.5 小时。
ObjectMapper
,但只获取 ObjectReader
/ ObjectWriter
实例(通过帮助方法),将这些读取器/写入器对象的引用保留在其他位置(或者动态调用)。这些读取器/写入器对象不仅在重新配置方面完全线程安全,而且非常轻量级(相对于映射器实例)。因此,保留数千个引用并不会增加太多内存使用。 - StaxManreadXxx()
的调用并不是阻塞的,可以完全并发进行;特别是对于readTree()
。 - StaxMan如果您不想将其定义为静态常量变量,但又想节省一些开销并保证线程安全,可以从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();
}
感谢作者。
ObjectMapper
将附加到可能属于池的线程上。 - Kenston Choi简而言之,应该避免使用静态变量,因为这使得编写简洁的单元测试变得困难。例如,使用静态final ObjectMapper时,您无法将JSON序列化替换为虚拟代码或空操作。
此外,静态final会阻止您在运行时重新配置ObjectMapper。您现在可能无法设想到原因,但如果您将自己锁定在静态final模式中,则除了拆除类加载器之外,没有其他方法可以让您重新初始化它。
在ObjectMapper的情况下,这是可以接受的,但通常来说,这是一种不好的实践,并且与使用单例模式或控制反转来管理长寿命对象相比没有任何优势。
readerFor
和writerFor
,可以根据需要创建ObjectReader
和ObjectWriter
实例。因此,我建议将映射器与初始配置放在某个静态位置,然后根据需要获取具有每个案例配置的读取器/写入器。 - CarighanObjectMapper
实例保存在一个线程安全的单例中:public final class JacksonObjectMapperHolder {
private static volatile JacksonObjectMapperHolder INSTANCE;
private static final Object MUTEX = new Object();
public static JacksonObjectMapperHolder getInstance() {
JacksonObjectMapperHolder instance = INSTANCE;
if(instance == null) {
synchronized(MUTEX) {
instance = INSTANCE;
if(instance == null) {
INSTANCE = instance = new JacksonObjectMapperHolder();
}
}
}
return instance;
}
private final ObjectMapper objectMapper = new ObjectMapper();
private JacksonObjectMapperHolder() {
super();
}
public final ObjectMapper getObjectMapper() {
return objectMapper;
}
}
final
的,所以隐式声明的构造函数对继承不是问题。然而,我想避免意外实例化,所以我明确地声明了构造函数并将其标记为private
。调用super
的繁琐表明了我避免隐式代码的选择。 - Olivercom.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)
ObjectMapper#setDateFormat()
后,ObjectMapper
是否仍然是线程安全的。众所周知,SimpleDateFormat
不是线程安全的(参见https://dev59.com/3Ww15IYBdhLWcg3wFHtZ),因此除非每次`writeValue()`之前都克隆例如`SerializationConfig`(我表示怀疑),否则`ObjectMapper`也不会是线程安全的。你能否消除我的担忧? - dma_kDateFormat
在底层确实是被克隆的。你的怀疑很好,但是不用担心。 :) - StaxManmapper.with()
调用(因为Jackson中的“with”意味着构建一个新实例和线程安全执行)。 但是关于配置更改:没有进行检查,因此必须保护对ObjectMapper
的配置访问。 至于“copy()”:是的,它创建了一个全新的副本,可以根据相同的规则进行完全重新配置,即先完全配置它,然后使用它,这是可以的。 这涉及到一些非平凡的成本(因为复制不能使用任何缓存处理程序),但这是安全的方式。 - StaxManObjectMapper
与ObjectReader
/ObjectWriter
类似不可变可能是有意义的:但这是一个重大的API更改,因此不能在2.x中完成。 另一种防止重新配置的方法是仅公开ObjectReader
和ObjectWriter
;只让系统的一小部分访问映射器。 - StaxMan