Java:使用NumberFormat时的同步问题?

22

我使用java.text.NumberFormat将数字转换为更易读的字符串,千位数用逗号隔开等等。基本上,我定义它如下:

public static NumberFormat nf = NumberFormat.getInstance(Locale.US);

我只需要在任何想要使数字可读的线程中调用nf.format(some_number)即可。 但是查看JavaDoc后,它说:“数字格式通常不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问格式,则必须在外部进行同步。”

如果我仅使用NumberFormat对象的format(number)方法,是否会存在同步问题?我尝试使用NumberFormat.getInstance(Locale.US).format(number)代替,但是我感到这样做每次都有开销,可能并不是真正需要的。这是否真的需要外部同步?还是有一种更简单、高效的方法可以在不使用NumberFormat的情况下实现相同的目的?

谢谢!

5个回答

23
即使您仅调用format方法,它仍然不是线程安全的。事实上,我们曾经因此在工作中出现过错误。通常我们要么动态创建NumberFormat对象,要么像Gerco建议的那样使用ThreadLocal。如果您想要更高级一些,可以继承NumberFormat类,在format方法中在调用委托NumberFormat的格式化方法之前进行同步,或者使用ThreadLocal检索委托。
但是,我认为最直接的方式,特别是如果您要连续格式化/解析多个数字,是手动使用ThreadLocal。

使用 https://gist.github.com/jontejj/5430320 进行了测量,结果发布在 http://1.microbenchmarks.appspot.com/run/jonatan.jonsson@softhouse.se/se.softhouse.common.numbers.NumberFormatBenchmark ,如果只有数字格式化,则增益实际上相当可观。尽管 NumberFormat 保留了已创建的格式化程序的缓存,并在返回它们之前仅克隆它们。 - jontejj
1
同样适用于所有子类...特别是DateFormat。 - Snicolas

15

使用ThreadLocal<NumberFormat>。这样每个线程都将拥有自己的私有NumberFormat实例,就不需要同步和仅需极小的开销。


4
查看NumberFormatDecimalFormat的源代码,似乎没有用于中间结果的字段。唯一的问题是格式本身(例如小数位数)可以通过setter进行更改,因此一个线程在处理format()调用时可能会更改它,这当然会导致混乱。
如果您从未使用过setter,则应该没问题,但这只是当前实现。与API文档相反,依赖于这一点可能会让人感到不舒服。使用ThreadLocal听起来像是一个很好的折衷方案。

1
我知道这是一个旧答案,但是...你真的不能只看源代码。即使你知道你正在使用哪个版本的Java,不同的供应商也会有所不同。(例如,我曾经看到IBM和Sun/Oracle JVM之间有很多差异。) - Adam Crume
在我的JDK版本中,实际上存在存储的中间结果,但正如你所说,情况可能有所不同。 - jontejj

2
NumberFormat是一个抽象类。调用getInstance时,默认返回一个DecimalFormat实例。DecimalFormat使用一些字段来维护其在格式化过程中的位置、前缀和后缀的模式,以及用于指示是否使用指数符号和千位分隔的布尔值,还有用于描述其整数和小数部分大小的int等。
如果需要进行并发格式化,则可以选择ThreadLocal选项。请注意,abstract Format类的所有子类都被认为是不线程安全的,因此格式化日期也应该这样小心处理。

1

没有理由共享NumberFormat对象。是的,它可能存在同步问题(查看您所在地区的源代码,您会发现它们使用成员变量进行格式化)。除非您遇到性能问题(这很可能不会发生),否则每次使用时都创建一个新的。

编辑正如Michael Borgwardt指出的那样,我的关于成员变量的猜测是不正确的。但仍然,为什么要担心呢?使用LocalThread,克隆NumberFormat,或者只是创建一个新的。就对象创建的效率而言,大多数情况下(但并非总是)都不是真正的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接