解決SimpleDateFormat的線程不安全問題的方法:ThreadLocal

問題 

我們寫了個DateUtil,內部有個SimpleDateFormat,是個static,我們想全局公用此常量

public class DateUtil {

    private static final String MESSAGE_FORMAT = "MM-dd HH:mm:ss.ms";

    private static final SimpleDateFormat format=new SimpleDateFormat(MESSAGE_FORMAT, Locale.getDefault());

    public static final DateFormat getDateFormat() {

        return format;

    }

}

如果要在多線程的環境下使用DateUtil,是會出問題的,SimpleDateFormat的parse方法是存在線程安全問題的,如果直接這麼使用,很可能會在SimpleDateFormat的parse方法內部崩潰


方法0:同步

把parse也封到工具類內,且對parse加同步鎖

public class DateUtil0 {

    private static final String MESSAGE_FORMAT = "MM-dd HH:mm:ss.ms";

    private static final SimpleDateFormat format=new SimpleDateFormat(MESSAGE_FORMAT, Locale.getDefault());

    public static final DateFormat getDateFormat() {

        return format;

    }

    public  Date parse(String str) {

        try {

            synchronized(format){

                return  format.parse(str);

            }

        } catch (ParseException e) {

        }

        return  null;

    }

}

這樣沒問題,但是加了鎖,效率嚴重下降。


方法1:使用ThreadLocal的泛型方法,推薦此方法

public class DateUtil1 {

    private static final ThreadLocal<DateFormat> messageFormat = new ThreadLocal<DateFormat>();

    private static final String MESSAGE_FORMAT = "MM-dd HH:mm:ss.ms";

    private static final DateFormat getDateFormat() {

        DateFormat format = messageFormat.get();

        if (format == null) {

            format = new SimpleDateFormat(MESSAGE_FORMAT, Locale.getDefault());

            messageFormat.set(format);

        }

        return format;

    }

}

hreadLocal是專門爲解決此類問題而存在的,當我們不想每次使用都new一個東西,就會使用static,一次創建,到處都能用,但是使用的時候往往又有線程安全問題。其實只要不同線程裏有一個不同的實例,就解決了這個問題。對於上例,每個線程創建一個SimpleDateFormat,互相獨立。線程內公用同一個,不同線程之間相互隔離.SimpleDateFormat。ThreadLocal就是根據上述思想被創建出來的。

核心思想在getDateFormat內部,首先去get,如果當前線程已有對應實例,就直接返回,如果沒有,就創建一個並返回。

方法2:ThreadLocal的匿名內部類寫法

public class DateUtil2 {

    private static final String MESSAGE_FORMAT = "MM-dd HH:mm:ss.ms";

    private static final ThreadLocal messageFormat = new ThreadLocal(){

        protected synchronized Object initialValue() {

            return  new SimpleDateFormat(MESSAGE_FORMAT, Locale.getDefault());

        }

    };

    private static final DateFormat getDateFormat() {

      return (DateFormat) messageFormat.get();

    }

}

getDateFormat會去調get方法,get方法內部檢查當前線程是否有實例存在,有就直接返回,無就調用initialValue來new一個,注意這裏initialValue要加同步鎖

方法3 匿名內部類返回null的寫法

1,2的雜交,將無實例就new寫在getDateFormat裏

public class DateUtil3 {

    private static final String MESSAGE_FORMAT = "MM-dd HH:mm:ss.ms";

    private static final ThreadLocal messageFormat = new ThreadLocal(){

        protected synchronized Object initialValue() {

            return  null;

        }

    };

    private static final DateFormat getDateFormat() {


        DateFormat df = (DateFormat) messageFormat.get();

        if (df == null) {

            df = new SimpleDateFormat(MESSAGE_FORMAT, Locale.getDefault());

            messageFormat.set(df);

        }

        return df;

    }

}

方法4,3的簡化

public class DateUtil4 {

    private static final String MESSAGE_FORMAT = "MM-dd HH:mm:ss.ms";

    private static final ThreadLocal messageFormat = new ThreadLocal();

    private static final DateFormat getDateFormat() {

        DateFormat df = (DateFormat) messageFormat.get();

        if (df == null) {

            df = new SimpleDateFormat(MESSAGE_FORMAT, Locale.getDefault());

            messageFormat.set(df);

        }

        return df;

    }

}

4和1比較,其實差別就在於一個使用泛型,一個使用強轉,最優方案還是1

覺得不錯請點贊支持,歡迎留言或進我的個人羣855801563領取【架構資料專題目合集90期】、【BATJTMD大廠JAVA面試真題1000+】,本羣專用於學習交流技術、分享面試機會,拒絕廣告,我也會在羣內不定期答題、探討

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章