JDK中的SimpleDateFormat線程非安全


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM