項目中,常常會用到日期操做。今天在項目中,運行發現多線程調用SimpleDateFormat,拋出異常的狀況,並且是選擇性的拋出,實際環境很難復現。java
咱們模擬如下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卻頻率拋出以下異常:dom
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並不是線程安全的,建議能夠採用如下方式解決:ide
解決方法:spa
a、使用sychronized
b、使用ThreadLocal
c、每次使用new SimpleDateFormat實例
d、也能夠使用joda-time等第三方庫線程