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

Che*_*eng 334 java json jackson

杰克逊图书馆的ObjectMapper课程似乎是线程安全的.

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

class Me {
    private static final ObjectMapper mapper = new ObjectMapper();
}
Run Code Online (Sandbox Code Playgroud)

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

class Me {
    private final ObjectMapper mapper = new ObjectMapper();
}
Run Code Online (Sandbox Code Playgroud)

Sta*_*Man 477

是的,这是安全的并且值得推荐.

您引用的页面中唯一的警告是,一旦共享,您就无法修改映射器的配置; 但你没有改变配置,所以没关系.如果你确实需要更改配置,你可以从静态块中执行此操作,它也可以.

编辑:(2013/10)

使用2.0及更高版本,可以通过注意到更好的方法来增强上述:使用ObjectWriterObjectReader可以构造的对象ObjectMapper.它们是完全不可变的,线程安全的,这意味着理论上甚至不可能导致线程安全问题(ObjectMapper如果代码尝试重新配置实例,可能会发生这种情况).

  • `DateFormat`确实是在引擎盖下克隆的.在那里很好的怀疑,但你被覆盖.:) (42认同)
  • @StaxMan:我有点担心在``ObjectMapper#setDateFormat()`]之后`ObjectMapper`仍然是线程安全的(http://jackson.codehaus.org/1.9.0/javadoc/org/codehaus/jackson/ map/ObjectMapper.html#setDateFormat%28java.text.DateFormat%29)被调用.众所周知[`SimpleDateFormat`不是线程安全的](http://stackoverflow.com/questions/6840803),因此除非在每个`writeValue()`之前克隆例如`SerializationConfig`,否则`ObjectMapper`将不会出现(我怀疑).你能揭穿我的恐惧吗? (20认同)
  • 我在大型企业应用程序的单元/集成测试中遇到了奇怪的行为.当把ObjectMapper作为静态最终类属性时,我开始面临PermGen问题.有人会关心解释可能的原因吗?我使用的是jackson-databind版本2.4.1. (2认同)
  • @MiklosKrivan你看过`ObjectMapper`了吗?!方法名为`writer()`和`reader()`(以及一些`readerFor()`,`writerFor()`). (2认同)
  • 没有`mapper.with()`调用(因为Jackson中的"with"意味着构造一个新实例,并且线程安全执行).但是关于配置更改:不进行检查,因此必须保护对"ObjectMapper"的配置访问.至于"copy()":是的,根据相同的规则创建一个可以完全(重新)配置的全新副本:首先完全配置它,然后使用,这很好.有非平凡的成本关联(因为复制不能使用任何缓存的处理程序),但它是安全的方式,是的. (2认同)
  • ...以及"为什么不看是否有人试图重新配置":这将需要额外的状态保持,同步.对于Jackson 3.x,使用builder-pattern可能是有意义的,使`ObjectMapper`不可变类似于`ObjectReader` /`ObjectWriter`:但这是主要的API更改所以无法用2.x完成.另一种防止重新配置的方法是只暴露`ObjectReader`s和`ObjectWriter; 保持对映射器的访问仅适用于系统的一小部分. (2认同)
  • @MasterJoe2 整个类的静态对象映射器(甚至只是 ObjectWriter)听起来比每次调用“toString()”时创建它更好。或者,如果有许多“toString()”需要跨多个类实现,甚至可以使用静态实例、方法来分离辅助类? (2认同)

小智 43

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

Map aPage = mapper.readValue(reader, Map.class);
Run Code Online (Sandbox Code Playgroud)

除此之外,表现并不好.当我用基于实例的变量替换静态变量时,停顿消失了,性能翻了两番.即,在40分钟.56秒内处理了2.4百万个JSON文档,而不是之前的2.5小时.

  • 加里的回答完全有道理.但是为每个类实例创建一个`ObjectMapper`实例可能会阻止锁定,但在以后的GC上可能会非常繁重(想象一下您创建的每个类实例的一个ObjectMapper实例).中间路径方法可以是,而不是在整个应用程序中只保留一个(公共)静态`ObjectMapper`实例,您可以在每个类**中声明`ObjectMapper`**的(私有)**静态**实例.这将减少全局锁定(通过按类分配负载),也不会创建任何新对象,因此也可以点亮GC. (12认同)
  • 我建议一个替代方法:保持静态`ObjectMapper`某处,但只获取`ObjectReader` /`ObjectWriter`实例(通过辅助方法),保留对其他地方(或动态调用)的引用.这些读取器/写入器对象不仅在重新配置时完全是线程安全的,而且非常轻量级(wrt映射器实例).因此,保留数千个引用并不会增加太多内存使用量. (11认同)
  • 所以对 ObjectReader 实例的调用不会阻塞,即在多线程应用程序中调用 objectReader.readTree,线程不会被阻塞等待另一个线程,使用 jackson 2.8.x (2认同)

小智 6

如果您不想将其定义为静态最终变量但想要节省一些开销并保证线程安全,我从这个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();
}
Run Code Online (Sandbox Code Playgroud)

归功于作者。

  • 但是存在内存泄漏的风险,因为“ObjectMapper”将附加到可能是池一部分的线程。 (2认同)
  • @IvanBalashov,但如果线程是从线程池(例如,Tomcat 之类的容器)创建/返回的,则它会保留。在某些情况下这可能是需要的,但我们需要注意一些事情。 (2认同)