ObjectMapper - 线程安全和性能的最佳实践

Posted

技术标签:

【中文标题】ObjectMapper - 线程安全和性能的最佳实践【英文标题】:ObjectMapper - Best practice for thread-safety and performance 【发布时间】:2019-12-31 09:16:24 【问题描述】:

总结

我想在下面描述的用例上下文中找到在线程安全和性能方面使用ObjectMapper 和/或ObjectReader 的最佳实践。

背景

我有一个辅助类 (Json.java),其中方法 toObject() 使用 ObjectMapperjson 字符串转换为给定 (json-mappable) 类的对象.

问题/疑问

我读到ObjectReader 通常被推荐为完全线程安全的,但我主要看到它在一个非泛型上下文中,其中要读取的类是预定义的。在这种情况下,您认为在线程安全和性能方面的最佳实践是什么?在代码中,我有三个建议可以作为起点。

我试图查看jackson-databind 的源代码和文档,但我的理论Java 技能不足以从中得出答案。我还查看了关于 SO 和其他地方的类似问题,但没有发现任何与我的情况足够接近的问题。

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;

public abstract class Json 

    private static final ObjectMapper jsonMapper = new ObjectMapper();
    
    // NOTE: jsonReader is only relevant for Suggestion 3.
    private static final ObjectReader jsonReader = jsonMapper.reader(); 

    // Suggestion 1:
    public static <T> T toObject1(final Class<T> type, final String json) throws IOException 
        return jsonMapper.readValue(json, type);
    

    // Suggestion 2:
    public static <T> T toObject2(final Class<T> type, final String json) throws IOException 
        return jsonMapper.readerFor(type).readValue(json);
    

    // Suggestion 3:
    public static <T> T toObject3(final Class<T> type, final String json) throws IOException 
        return jsonReader.forType(type).readValue(json);
    

    // Remainder of class omitted for brevity.

【问题讨论】:

我一直使用建议#1。这让映射器可以找出执行反序列化的最佳方式 @SharonBenAsher 为什么?它有什么不同? 【参考方案1】:

关于并发

ObjectMapperObjectReader 在这里不相关。ObjectReader 看起来对您的方案没有帮助。 它的规范说:

可用于每个序列化配置的 Builder 对象 反序列化参数,例如要使用的根类型或要使用的对象 更新(而不是构建新实例)。

请注意,ObjectMapperObjectReader 的两个实例都是线程安全的,前提是它们的配置在序列化/反序列化客户端调用之间没有更改。ObjectReader 确实指定了:

映射器实例是完全线程安全的,前提是所有配置 实例在任何读取或写入调用之前发生。

虽然ObjectReader 的不同之处在于更新其配置将使其返回其文档所述的新实例的方式是不可变的:

使用“突变工厂”模式,以便实例是不可变的(并且 因此完全线程安全,没有外部同步);新的 实例是为不同的配置构建的。

根据您的要求,您不想更改客户端调用之间的配置。所以使用ObjectMapper 看起来更相关。 因此,我将消除 3) 方式和 2) 方式,因为 jsonMapper.readerFor(type)ObjectReader 实例的工厂方法。您仍然可以在这里使用ObjectReader

所以最简单通用的方式看起来更好:

// Suggestion 1:
public static <T> T toObject1(final Class<T> type, final String json) throws IOException 
    return jsonMapper.readValue(json, type);
 

关于性能

此外,请记住 ObjectReader 是不可变的。因此,2 和 3 方法在每次调用时都会创建 ObjectReader 的新实例。 它看起来不是一个很好的性能提示。 是的,这些都是轻量级对象,但每次创建它们都是有代价的。ObjectReader 文档说:

Instances 最初是由 ObjectMapper 构造的,可以重复使用, 共享,缓存;既因为线程安全又因为实例 相对较轻。

在那里你不重用这些实例。因此,您将失去缓存和性能方面的任何好处。 您可以将它们存储到Map 字段中并重复使用它们,但只有在您需要提高ObjectMapper 的实际性能并且当然在得出任何结论之前进行测量时才这样做。

结论:对于您的用例,我认为第一个解决方案的性能和并发性更好 (ObjectMapper)

【讨论】:

这可能对其他情况也有帮助。正如javadoc所说:“可用于反序列化参数的每个序列化配置的构建器对象,例如要使用的根类型或要更新的对象(而不是构造新实例)。”例如ObjectReader.withValueToUpdate(Object) 是使用它的一个很好的理由。 ttps://***.com/questions/31005176/jackson-gson-apply-map-to-pojo 此类场景请勿进入。 @davidxxx 我猜“实例最初由 ObjectMapper 构建”的意思是“您通常使用 ObjectMapper 创建实例”。这并不意味着 ObjectMapper 在内部管理(例如缓存)这些实例。事实上,我找不到存储在ObjectMapper中的ObjectReader... @Andrew Tobilko 当然。这将是不可取的。我的观点是,在每次调用时创建 ObjectReader 是有代价的,因为它没有被缓存。所以在目前的情况下,它看起来没有必要。 @davidxxx 这将是非常可取的(对于相同的配置),不是吗? @davidxxx 我的意思是ObjectMapper 也不缓存任何东西。 “任何东西”是指ObjectReader\Writer 的任何实例【参考方案2】:
private static final ObjectMapper jsonMapper = new ObjectMapper();

构造ObjectMapper 实例是一项相对昂贵的操作,因此建议创建一个对象并重用它。你做对了final

// Suggestion 1:
public static <T> T toObject1(final Class<T> type, final String json) throws IOException 
    return jsonMapper.readValue(json, type);

您总是读取 JSON 到 POJO,所以让我们准确而清晰地使用 ObjectReader

// Suggestion 2:
public static <T> T toObject2(final Class<T> type, final String json) throws IOException 
    return jsonMapper.readerFor(type).readValue(json);


// Suggestion 3:
public static <T> T toObject3(final Class<T> type, final String json) throws IOException 
    return jsonReader.forType(type).readValue(json);

真的没有区别。两种方法都将构造一个新的ObjectReader 对象:前者(jsonMapper.readerFor(type))将直接为您提供一个完全构建的实例,后者(jsonReader.forType(type))将补充尚未使用的jsonReader 并返回一个就绪- 使用对象。我宁愿选择选项 2,因为我不想保留该字段。

您不必担心性能或线程安全。尽管创建 ObjectMapper 可能代价高昂(或从中制作副本),但获取和使用 ObjectReaders 是轻量级且完全线程安全的。

来自the Java documentation(强调我的):

使用“突变工厂”模式,使实例不可变(因此完全线程安全,无需外部同步);为不同的配置构建新实例。实例最初由ObjectMapper构造,可以复用、共享、缓存;既是因为线程安全,也是因为实例相对轻量级

我最近自己也有这些问题,并决定将ObjectMapper#reader(InjectableValues) 作为工厂方法。这非常方便,特别是当您想稍微自定义 ObjectReader 或者像我一样调整 DeserializationContext 时。

顺便说一句,这是一个很好的问题。

【讨论】:

哇,感谢您的详细回答!您的建议与 @davidxxx 和 Sharon Ben Asher 给出的建议不同,但您似乎真的在这里完成了您的研究,所以我倾向于相信您。 @axeleration 他们的观点很好,我认为任何一个选项都很好(性能/线程安全)【参考方案3】:

正如我在评论中提到的,我一直使用建议 #1。我不知道选项之间在线程安全/性能方面是否存在差异。

但是,如果目标类型本身是使用泛型类型参数化的,则此方法将不起作用。最明显的用法是一些集合:

Json.toObject1(List.class, str);  // will deserialize into List<Object>

为此,您必须使用 Jackson 的 TypeReference

// Suggestion 4:
public static <T> T toObject4(final TypeReference<T> typeRef, final String json) throws IOException 
    return jsonMapper.readValue(json, typeRef);


Json.toObject4(new TypeReference<List<SomeClass>>(), str);  // will deserialize into List<SomeClass>

【讨论】:

感谢您的回答!我实际上知道 TypeReference 案例并且为此有一个重载的 toObject() ,但为了简洁起见,我决定不将它包含在问题中。澄清一下,我不相信线程安全或性能实际上对我在这里的工作很重要,但我发现这个库很吸引人,并希望更好地理解它。

以上是关于ObjectMapper - 线程安全和性能的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

C++最佳实践 | 6. 性能

C++最佳实践 | 3. 安全性

C++最佳实践 | 3. 安全性

简单测试Java线程安全中阻塞同步与非阻塞同步性能

C++最佳实践 | 5. 可移植性及多线程

C++最佳实践 | 5. 可移植性及多线程