在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類庫來處理。