项目中,经常会用到日期操作。今天在项目中,运行发现多线程调用SimpleDateFormat,抛出异常的情况,而且是选择性的抛出,实际环境很难复现。
我们模拟以下2种场景:
a、单实例场景1:
final DateFormat df = new SimpleDateFormat("yyyyMMdd,HHmmss"); ExecutorService ts = Executors.newFixedThreadPool(100); for (;;) { ts.execute(new Runnable() { @Override public void run() { try { df.format(new Date(new Random().nextLong())); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } }); }
b、多实例场景2:
final ThreadLocal<DateFormat> tl = new ThreadLocal<DateFormat>(){ @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd,HHmmss"); } }; ExecutorService ts = Executors.newFixedThreadPool(100); for (;;) { ts.execute(new Runnable() { @Override public void run() { try { tl.get().format(new Date(new Random().nextLong())); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } }); }
运行结果对比可以看到,场景2可以稳定运行,而场景1却频率抛出如下异常:
java.lang.ArrayIndexOutOfBoundsException: 2397709 at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:454) at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2333) at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2248) at java.util.Calendar.setTimeInMillis(Calendar.java:1140) at java.util.Calendar.setTime(Calendar.java:1106) at java.text.SimpleDateFormat.format(SimpleDateFormat.java:955) at java.text.SimpleDateFormat.format(SimpleDateFormat.java:948) at java.text.DateFormat.format(DateFormat.java:336) at foo.Bar$1.run(Bar.java:24) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744)
看出 SimpleDateFormat.format并非线程安全的,建议可以采用以下方式解决:
解决方法:
a、使用sychronized
b、使用ThreadLocal
c、每次使用new SimpleDateFormat实例
d、也可以使用joda-time等第三方库