这是使java.text.DateFormat线程安全的正确方法吗?

这是使java.text.DateFormat线程安全的正确方法吗?,java,date,Java,Date,我正在处理一个项目,其中日期格式保存为静态实用程序字段,如下所示 public static final SimpleDateFormat MM_DD_YYYY = new SimpleDateFormat(DATE_FORMAT_DEFAULT, Locale.US); 幸运的是,FindBugs开始发出警告,DateFormats对于多线程使用来说本质上是不安全的 为了删除这些警告,团队中的一位开发人员将字段访问权限更改为private,并提供了如下的公共访问函数 private stat

我正在处理一个项目,其中日期格式保存为静态实用程序字段,如下所示

public static final SimpleDateFormat MM_DD_YYYY = new SimpleDateFormat(DATE_FORMAT_DEFAULT, Locale.US);
幸运的是,FindBugs开始发出警告,DateFormats对于多线程使用来说本质上是不安全的

为了删除这些警告,团队中的一位开发人员将字段访问权限更改为private,并提供了如下的公共访问函数

private static final SimpleDateFormat MM_DD_YYYY = new SimpleDateFormat(DATE_FORMAT_DEFAULT, Locale.US);
public static SimpleDateFormat getMmDdYyyy() {
    return MM_DD_YYYY;
}
神奇地发现布格警告消失了! 我的问题是,这两段代码在语义上不是相同的吗

如果是,为什么Findbugs在第二种情况下不显示任何警告?

好的一面是,使用公共字段几乎总是一个坏主意,尤其是对于可变对象

但是,它肯定仍然不是线程安全的。两个线程完全可以调用该方法,并最终同时使用格式化程序

您可以创建自己的包装器对象(可能仍在扩展
DateFormat
;我不能马上确定),该对象序列化了请求,并且可能有一个底层格式的“池”。或者您可以使用Joda Time或
java.Time
,这两种API都是更好的日期/时间API,并且都有线程安全的格式化程序


另一种选择是只使用两个静态方法,
parseMonthDayYear
formatMonthDayYear
,让它们负责线程安全。

您可以使用ThreadLocal:

private static ThreadLocal<SimpleDateFormat> format = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat(DATE_FORMAT_DEFAULT, Locale.US);
    }
};
private static ThreadLocal format=new ThreadLocal(){
@凌驾
受保护的SimpleDataFormat初始值(){
返回新的SimpleDataFormat(日期\格式\默认值,Locale.US);
}
};

在AD安全上下文中使用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);
 }
private ThreadLocal df=new ThreadLocal(){
@凌驾
公共日期格式get(){
返回super.get();
}
@凌驾
受保护的日期格式初始值(){
返回新的SimpleDataFormat(“yyyy-MM-dd”);
}
@凌驾
公共空间删除(){
super.remove();
}
@凌驾
公共无效集(日期格式值){
super.set(值);
}
};
公共日期转换器StringToDate(字符串dateString)引发ParseException{
返回df.get().parse(日期字符串);
}
查看此链接了解更多详细信息和基准:
SimpleDataFormat不是线程安全的,因为它在处理时使用内部可变状态。将其设置为最终静态将不会有帮助,因为操作仍将在单个实例上执行,在该实例中,线程将通过该状态进行交互

解决方案:返回防御性副本:

private static final DateFormat MM_DD_YYYY = new SimpleDateFormat(DATE_FORMAT_DEFAULT, Locale.US);

public static DateFormat getMmDdYyyy() {
    return (DateFormat) MM_DD_YYYY.clone();
}

这只是一个实验,但我想知道是否还有一个额外的解决方案可以创建一个
SimpleDateFormat
子类来覆盖所有
set*()
方法,而不使用op实现,然后使用它?在我看来,
SimpleDateFormat
线程安全问题来自于格式的易变性。(我无法想象在实际的
format()
调用过程中会出现任何状态。)因此,如果您使用的是一个
SimpleDataFormat
,您知道它永远不会更改,那么您可能会改用这个子类?您甚至可以在构造函数中传递一个
simpleDataFormat
实例。这有点像是一个不可变的装饰器,一个使用
java.util.Collections.unmodifiable*
方法的工具

另外,您不必确保每个线程都有一个实例,也不必有同步开销。缺点是(我认为)这可能不能满足FindBugs(我没有,所以我不能尝试)

更新-为什么这不起作用
结果证明这行不通。正如@biziclop在下面的评论中指出的,实际上
format()
方法中使用了可变的内部状态-一个
日历的共享实例@biziclop提供了显示
SimpleDataFormat
源代码中发生了什么的功能。我不会猜测实现者为什么会这样做,但我可以肯定地说,即使是
format()
也是有状态的,因此我的计划也不会起作用。

为什么不在
getmmddyyyyy()
上同步呢?@lostmind:因为我只是在回答你的答案,这没有帮助。最终还是会有多个线程引用同一个“线程不安全”对象。你不会把危险的部分连载是的。。我意识到一旦我被否决:)。。同步不会帮助线程仍然可以访问该实例。。包装纸应该起作用。。谢谢:)你会这么想的,但我有你要的。查看链接的代码以及它如何使用共享的
日历
字段。SDF的问题是,如果两个线程(例如)解析字符串,它们通过内部状态进行交互。同步或使其不可变都无济于事。@biziclop egads,他们为什么这样做?谢谢你捕捉和展示这个!将立即编辑。我真的不想猜。:)@sparc_spread糟糕的设计,与
Date
类一样是可变的。