我正在开展一个项目,其中日期格式保存为静态实用程序字段,如下所示
public static final SimpleDateFormat MM_DD_YYYY = new SimpleDateFormat(DATE_FORMAT_DEFAULT, Locale.US);
幸运的是,FindBugs开始发出警告,警告说DateFormats对于多线程使用本质上是不安全的。
要删除这些警告,团队中的一位开发人员将字段访问权限更改为私有,并提供了这样的公共访问者功能
private static final SimpleDateFormat MM_DD_YYYY = new SimpleDateFormat(DATE_FORMAT_DEFAULT, Locale.US);
public static SimpleDateFormat getMmDdYyyy() {
return MM_DD_YYYY;
}
神奇的FindBugs警告消失了! 我的问题是,两个代码片段在语义上是否相同?
如果是,为什么Findbugs在第二种情况下没有显示任何警告?
答案 0 :(得分:3)
在thead安全上下文中使用DateFormat的最好和更快的方法是在ThreadLocal中使用它。它将为每个线程确保一个实例。
像这样: private ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat> () {
@Override
public DateFormat get() {
return super.get();
}
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy MM dd");
}
@Override
public void remove() {
super.remove();
}
@Override
public void set(DateFormat value) {
super.set(value);
}
};
public Date convertStringToDate(String dateString) throws ParseException {
return df.get().parse(dateString);
}
查看此链接以获取更多详细信息和基准: http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html
答案 1 :(得分:2)
嗯,更好因为拥有公共字段几乎总是一个坏主意,特别是对于可变对象。
但是,肯定仍然不是线程安全的。两个线程绝对可以调用该方法并最终同时使用格式化程序。
你可以创建你自己的包装器对象(可能仍然在扩展DateFormat
;我不确定是否随意)哪些序列化请求并且可能具有底层格式的“池”。或者您可以使用Joda Time或java.time
,这两者都是更好的日期/时间API,并且具有线程安全的格式化程序。
另一种选择是只有两个静态方法parseMonthDayYear
和formatMonthDayYear
,让他们处理线程安全。
答案 2 :(得分:1)
您可以使用ThreadLocal:
private static ThreadLocal<SimpleDateFormat> format = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(DATE_FORMAT_DEFAULT, Locale.US);
}
};
答案 3 :(得分:0)
SimpleDateFormat
不是线程安全的,因为它在处理时使用内部可变状态。使final static
无法帮助,因为仍将对该单个实例执行操作,其中通过该状态线程将进行交互。
解决方案:返回防御性副本:
private static final DateFormat MM_DD_YYYY = new SimpleDateFormat(DATE_FORMAT_DEFAULT, Locale.US);
public static DateFormat getMmDdYyyy() {
return (DateFormat) MM_DD_YYYY.clone();
}
答案 4 :(得分:0)
这只是一个实验,但我想知道一个额外的解决方案是否可以创建一个SimpleDateFormat
子类来覆盖所有set*()
方法并使用no-op实现,并使用它?在我看来,SimpleDateFormat
线程安全问题来自格式的可变性。 (我无法想象在实际的format()
调用过程中会有任何状态。)因此,如果您使用的是您知道永远不会改变的SimpleDateFormat
,那么可以使用此子类代替?你甚至可以在构造函数中传递SimpleDateFormat
实例。这将是一种不可变的装饰器,你可以使用java.util.Collections.unmodifiable*
方法。
加分是你不必确保每个线程有一个实例,或者有同步开销。减号(我认为)这可能不会满足FindBugs(我没有它,所以我无法尝试。)
事实证明这不会奏效。正如@biziclop在下面的评论中指出的那样,format()
方法中实际使用了 可变内部状态 - Calendar
的共享实例。 Here is the link @biziclop提供了显示SimpleDateFormat
来源的内容。我不会推测为什么实现者会这样做,但我可以肯定地说,即使format()
是有状态的,因此我的计划也不起作用。