在JDK中使用SimpleDateFormat的时候都会遇到线程安全的问题,在JDK文档中也说明了该类是线程非安全的,建议对于每个线程都创建一个SimpleDateFormat对象。如下面一个Case中,多个线程去调用SimpleDateFormat中得parse方法:
@Test public void testUnThreadSafe() throws Exception { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,S"); final String[] dateStrings = { "2014-04-30 18:51:01,61", "2014-04-30 18:51:01,461", "2014-04-30 18:51:01,361", "2014-04-30 18:51:01,261", "2014-04-30 18:51:01,161", }; int threadNum = 5; Thread[] parseThreads = new Thread[threadNum]; for (int i=0; i<threadNum; i++) { parseThreads[i] = new Thread(new Runnable() { public void run() { for (int j=0; j<dateStrings.length; j++) { try { System.out.println(Thread.currentThread().getName() + " " + sdf.parse(dateStrings[j])); } catch (ParseException e) { e.printStackTrace(); } } } }); parseThreads[i].start(); } for (int i=0; i<threadNum; i++) { parseThreads[i].join(); } }
将会抛出异常:java.lang.NumberFormatException: multiple points
通常的解决办法有:
1. 使用synchronized
synchronized (sdf) { System.out.println(Thread.currentThread().getName() + " " + sdf.parse(dateStrings[j])); }
2. 每用一次实例化一次
try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,S"); System.out.println(Thread.currentThread().getName() + " " + sdf.parse(dateStrings[j])); } catch (ParseException e) { e.printStackTrace(); }
3. 使用ThreadLocal
@Test public void testUnThreadSafe() throws Exception { // final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,S"); final ThreadLocal<SimpleDateFormat> localSdf = new ThreadLocal<SimpleDateFormat>(); final String[] dateStrings = { "2014-04-30 18:51:01,61", "2014-04-30 18:51:01,461", "2014-04-30 18:51:01,361", "2014-04-30 18:51:01,261", "2014-04-30 18:51:01,161", }; int threadNum = 5; Thread[] parseThreads = new Thread[threadNum]; for (int i=0; i<threadNum; i++) { parseThreads[i] = new Thread(new Runnable() { public void run() { for (int j=0; j<dateStrings.length; j++) { try { SimpleDateFormat sdf = localSdf.get(); if (sdf == null) { sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,S"); localSdf.set(sdf); } System.out.println(Thread.currentThread().getName() + " " + sdf.parse(dateStrings[j])); } catch (ParseException e) { e.printStackTrace(); } } } }); parseThreads[i].start(); } for (int i=0; i<threadNum; i++) { parseThreads[i].join(); } }
第一种和第二种解决方案对于一个工具类来说都会带来昂贵的资源开销,建议使用ThreadLocal创建一个对单个线程来说全局的变量,保证线程安全,当然可以使用第三方工具类如Apache commons 里的FastDateFormat或者Joda-Time类库来处理。