这是制作java.text.DateFormat threadSafe的正确方法吗?

时间:2015-05-07 13:42:55

标签: java date

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

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在第二种情况下没有显示任何警告?

5 个答案:

答案 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,并且具有线程安全的格式化程序。

另一种选择是只有两个静态方法parseMonthDayYearformatMonthDayYear,让他们处理线程安全。

答案 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()是有状态的,因此我的计划也不起作用。