我们知道 dateformat 类不是线程安全的。我有一个多线程场景,需要使用dateformats。我无法在新线程中创建新实例,因为 SimpledateFormat 创建似乎很昂贵(构造函数最终调用“编译”,这是昂贵的)。经过一些测试后,我只剩下两个选项:
有什么建议吗?
如果大家之前已经面对过这个问题,你采取了什么方向。
注意:之前曾询问similar question,但它已关闭指向apache包。我不能为此使用新库。 我也读过类似的question on SO
答案 0 :(得分:13)
如果您创建了一个类,它会以循环方式使用固定大小的预先创建的SimpleDateFormat对象格式化日期?鉴于无争议同步很便宜,这可以在SimpleDateFormat对象上同步,从而在整个集合中分摊冲突。
因此,可能会有50个格式化程序,每个格式化程序轮流使用 - 因此只有当51个日期实际同时格式化时才会发生锁争用。
编辑2011-02-19(太平洋标准时间)
我按照上面的建议实现了一个固定池,其代码(包括测试)为available on my website。
以下是在Java 6 SE客户端JVM中运行的四核AMD Phenom II 965 BE的结果:
2011-02-19 15:28:13.039 : Threads=10, Iterations=1,000,000
2011-02-19 15:28:13.039 : Test 1:
2011-02-19 15:28:25.450 : Sync : 12,411 ms
2011-02-19 15:28:37.380 : Create : 10,862 ms
2011-02-19 15:28:42.673 : Clone : 4,221 ms
2011-02-19 15:28:47.842 : Pool : 4,097 ms
2011-02-19 15:28:48.915 : Test 2:
2011-02-19 15:29:00.099 : Sync : 11,184 ms
2011-02-19 15:29:11.685 : Create : 10,536 ms
2011-02-19 15:29:16.930 : Clone : 4,184 ms
2011-02-19 15:29:21.970 : Pool : 3,969 ms
2011-02-19 15:29:23.038 : Test 3:
2011-02-19 15:29:33.915 : Sync : 10,877 ms
2011-02-19 15:29:45.180 : Create : 10,195 ms
2011-02-19 15:29:50.320 : Clone : 4,067 ms
2011-02-19 15:29:55.403 : Pool : 4,013 ms
值得注意的是,克隆和汇集非常接近。在重复运行中,克隆速度快于汇集速度,因为它的速度较慢。当然,这项测试是为极端争用而刻意设计的。
在SimpleDateFormat的特定情况下,我想我可能只想创建一个模板并按需克隆它。在更一般的情况下,我可能想要使用这个池来做这些事情。
在以某种方式做出最终决定之前,我想彻底测试各种JVM,版本以及各种这类对象。较旧的JVM以及手持设备和手机等小型设备上的JVM可能会在对象创建和垃圾收集方面产生更多开销。相反,他们可能在无争议的同步中有更多的开销。
FWIW,从我对代码的评论来看,似乎SimpleDateFormat很可能在克隆时做的工作量最大。
编辑2011-02-19(太平洋标准时间)
同样有趣的是无竞争的单线程结果。在这种情况下,池与单个同步对象相同。这意味着该池是整体上最好的替代方案,因为它在满足和无条件时提供出色的性能。有点令人惊讶的是,单线程时克隆效果不佳。
2011-02-20 13:26:58.169 : Threads=1, Iterations=10,000,000
2011-02-20 13:26:58.169 : Test 1:
2011-02-20 13:27:07.193 : Sync : 9,024 ms
2011-02-20 13:27:40.320 : Create : 32,060 ms
2011-02-20 13:27:53.777 : Clone : 12,388 ms
2011-02-20 13:28:02.286 : Pool : 7,440 ms
2011-02-20 13:28:03.354 : Test 2:
2011-02-20 13:28:10.777 : Sync : 7,423 ms
2011-02-20 13:28:43.774 : Create : 31,931 ms
2011-02-20 13:28:57.244 : Clone : 12,400 ms
2011-02-20 13:29:05.734 : Pool : 7,417 ms
2011-02-20 13:29:06.802 : Test 3:
2011-02-20 13:29:14.233 : Sync : 7,431 ms
2011-02-20 13:29:47.117 : Create : 31,816 ms
2011-02-20 13:30:00.567 : Clone : 12,382 ms
2011-02-20 13:30:09.079 : Pool : 7,444 ms
答案 1 :(得分:3)
由于在您的情况下无法使用ThreadLocal,因此您应该使用池。获取或创建一个新实例,使用它,然后将其放入池中。
答案 2 :(得分:2)
我使用ThreadLocal来存储我使用的每种日期格式的实例。
答案 3 :(得分:2)
正如您所链接的类似问题所建议的那样,它实际上取决于您的用例。如果您正在重用线程,那么使用ThreadLocal值将是您最好的选择。如果您不重用线程,则必须权衡同步与对象创建。如果您的同步任务长时间运行(在您需要一次解析许多日期的情况下),您的线程可能最终等待的时间比创建对象所需的时间长。
就个人而言,我一般都没有使用ThreadLocal值的路线,但这是因为我的所有用例都是使用线程重用。
答案 4 :(得分:2)
我们已切换到使用线程安全的FastDateFormat
答案 5 :(得分:2)
必须指出选项2: “克隆每个帖子 - 不知道是否有一些捕获?” 只是一个可行的选项,因为SimpleDateFormat和DateFormat实现了深 clone()。 浅克隆不再仅仅使用相同的实例进行线程安全。
DateFormat和任何子类不是线程安全的原因是由于DateFormat使用作为成员变量存储的Calendar的内部实例。对format()或parse()的调用依赖于Calendar实例上的clear(),set()和get()字段。任何并发调用都可能破坏内部日历状态。
事实上,这些类实现了一个深 clone(),使其相对较慢。
答案 6 :(得分:0)
我会有一个可以锁定的SimpleDateFormats的小缓存。每次锁定都需要1-2微秒,但您可能希望每次花费多少时间来创建新对象。