Java 線程不安全的SimpleDateFormat

SimpleDateFormat是Java提供的一個格式化和解析日期的工具類
可是因爲它是線程不安全的,多線程共用一個SimpleDateFormat實例對日期進行解析或者格式化會致使程序出錯java

問題重現

public class TestSimpleDateFormat {
    //(1)建立單例實例
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) {
        //(2)建立多個線程,並啓動
        for (int i = 0; i <100 ; ++i) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {//(3)使用單例日期實例解析文本
                        System.out.println(sdf.parse("2017-12-13 15:17:27"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();//(4)啓動線程
        }
    }
}安全

代碼(1)建立了SimpleDateFormat的一個實例,
代碼(2)建立100個線程,每一個線程都公用同一個sdf對象對文本日期進行解析,
多運行幾回就會拋出java.lang.NumberFormatException異常,
加大線程的個數有利於該問題復現。多線程


問題分析

SimpleDateFormat實例裏面有一個Calendar對象
SimpleDateFormat之因此是線程不安全的就是由於Calendar是線程不安全的
而Calendar之因此是線程不安全的是由於其中存放日期數據的變量都是線程不安全的,好比裏面的fields,time等併發

解決方案

第一種方式:每次使用時候new一個SimpleDateFormat的實例,這樣能夠保證每一個實例使用本身的Calendar實例,可是每次使用都須要new一個對象,而且使用後因爲沒有其它引用,就會須要被回收,開銷會很大。
第二種方式:能夠使用synchronized進行同步,但使用同步意味着多個線程要競爭鎖,在高併發場景下會致使系統響應性能降低。ide


Thread thread = new Thread(new Runnable() {
    public void run() {
        try {// (3)使用單例日期實例解析文本
            synchronized (sdf) {
                System.out.println(sdf.parse("2017-12-13 15:17:27"));
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
});高併發


第三種方式:使用ThreadLocal,這樣每一個線程只須要使用一個SimpleDateFormat實例相比第一種方式大大節省了對象的建立銷燬開銷,而且不須要對多個線程直接進行同步,使用ThreadLocal方式代碼以下:工具


public class TestSimpleDateFormat2 {
    // (1)建立threadlocal實例
    static ThreadLocal<DateFormat> safeSdf = new ThreadLocal<DateFormat>(){
        @Override 
        protected SimpleDateFormat initialValue(){
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    
    public static void main(String[] args) {
        // (2)建立多個線程,並啓動
        for (int i = 0; i < 10; ++i) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {// (3)使用單例日期實例解析文本
                            System.out.println(safeSdf.get().parse("2017-12-13 15:17:27"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();// (4)啓動線程
        }
    }
}性能


代碼(1)建立了一個線程安全的SimpleDateFormat實例,步驟(3)在使用的時候首先使用get()方法獲取當前線程下SimpleDateFormat的實例,在第一次調用ThreadLocal的get()方法適合會觸發其initialValue方法用來建立當前線程所須要的SimpleDateFormat對象。spa

總結

SimpleDateFormat是線程不安全的,應該避免多線程下使用SimpleDateFormat的單個實例,多線程下使用時候最好使用ThreadLocal對象.net

轉自:http://ifeve.com/notsafesimpledateformat/

相關文章
相關標籤/搜索