2014-11-21 35 views
2

我正在对日期格式代码进行一些重构,因为我们设法因各种原因引入了许多不一致的地方。我知道最好有一个ThreadLocalSimpleDateFormat。在讨论这里之后,我们不确定在Enum中使用ThreadLocal是否是必需的,因为我们从不改变实例并且不公开它,因此它不能被突变?如果它是还需要做一个Enum,像这样,破坏什么?其他事情是否已经破裂或者没有做到我认为应该做的事情?基本上我没有必须使用ThreadLocal进行很多工作,我不确定这些影响,特别是它与Enum的交互方式。Enum中的ThreadLocal SimpleDateFormat?

public enum DateFormat { 
DATE(newThreadLocalSimpleDateFormat("MM/dd/yyyy")), 
LONG_DATE(newThreadLocalSimpleDateFormat("MMMM dd, yyyy")), 
TIMESTAMP(newThreadLocalSimpleDateFormat("MM/dd/yyyy hh:mm:ss aa")); 

private transient final ThreadLocal<SimpleDateFormat> formatter; 

DateFormat(final ThreadLocal<SimpleDateFormat> formatter) { 
    this.formatter = formatter; 
} 


public String format (final Date date) { 
    return this.formatter.get().format(date); 
} 

private static ThreadLocal<SimpleDateFormat> newThreadLocalSimpleDateFormat(final String frmtString) { 
    return new ThreadLocal<SimpleDateFormat>() { 
     @Override 
     protected SimpleDateFormat initialValue() { 
      return new SimpleDateFormat(frmtString); 
     } 
    }; 
} 

} 
+2

'SimpleDateFormat'不是线程安全的。事实上,你在枚举中引用了一个没有任何区别。 – 2014-11-21 16:05:49

+0

如果你不改变SDF的状态,那么你不需要ThreadLocal。 – SMA 2014-11-21 16:08:18

+3

@almas这不够好。解析方法也修改SDF的状态。 – 2014-11-21 16:12:07

回答

2

这里的enum是一个全局常量,它被用作一种方便的方式从threadlocal变量中检索正确的格式化程序。枚举是不可变的,但是如果它引用了可变状态的事物,那么这些事情仍然会有问题。

当你说

我们永远不变的情况下,不要暴露它,所以它不能突变

你误解的线程安全问题的性质。问题是SimpleDateFormat的内部状态不受多线程访问的保护,因此当多个线程访问相同的格式化程序实例时,这些线程中的任何一个都可以改变其他并发线程操纵的状态,从而破坏结果。

您处理这个格式的选择是:

  • 离开ThreadLocal的完好,这样每个线程都有自己的格式复印件;在某些情况下,最大的危险是threadlocal对象可能无法正确清理,因此从池中选择一个线程(您正在使用线程池,对吧?)可能有一个与以前的使用相关的变量,但在这种情况下,它可能更像是一个特征:如果所有线程都需要这个,最好是格式化程序附近,

  • 为每个调用以便不共享任何东西(快速,简单和线程安全,但是这会在格式化程序丢失时创建垃圾;使垃圾收集器更加努力会降低性能),

  • 同步对格式化程序的访问,无法同时访问它(可能是最不具吸引力的选择,这很可能导致瓶颈)

  • u唱一个不同的DateFormat实现,就像FastDateFormat,它是线程安全的(线程安全可能意味着实现是锁定或复制状态,所以可能会有缺点,需要一些测试才能看到结果)。

保留现有的ThreadLocal或签出线程安全格式化程序的备选方案可能是最佳选择。保持你拥有的东西看起来是一个安全的选择。

没有线程池,Threadlocal变得不那么吸引人,因为线程的生命周期更短,因此给定的格式化程序的重用性更低。线程池由于多种原因是一个好主意(主要是确保一个错误条件不会导致你的应用程序在线程之外运行,这样你的应用程序可以以受控的方式降级),如果你不使用它们,你应该。

对我来说,关于这段代码的最奇怪的事情是枚举必须是可序列化的,但threadLocal不是可序列化的,所以它必须声明为transient。如果这件事没有得到序列化,并因此出于某种原因反序列化,反序列化的副本将有一个null threadLocal。实际上,这不是你想要序列化的任何东西(你不会将它存储在HttpSession中或将它传递给另一个JVM),因此它看起来像是一个小到不存在的风险。

2

您目前的执行情况良好,您不应通过删除ThreadLocal来更改它。 SimpleDateFormat不是线程安全的。换句话说,在没有外部同步的情况下跨线程使用相同的实例将不会产生您期望的行为。 javadoc对此警告你。

ThreadLocal实例通过为每个线程提供单个实例来工作。此实例不共享(除非您共享它),因此不会导致任何问题。

事实上,它在enum没有区别。