1. 程式人生 > >多執行緒SimpleDateFormat日期格式化與ThreadLocal用法

多執行緒SimpleDateFormat日期格式化與ThreadLocal用法

前言

SimpleDateFormat 是 JDK 提供的一個日期格式化和解析類,但它是非執行緒安全的,原因如下。

1. parse方法

        Date parsedDate;
        try {
            parsedDate = calb.establish(calendar).getTime();
            // If the year value is ambiguous,
            // then the two-digit year == the default start year
            if (ambiguousYear[0]) {
                if (parsedDate.before(defaultCenturyStart)) {
                    parsedDate = calb.addYear(100).establish(calendar).getTime();
                }
            }
        }
        // An IllegalArgumentException will be thrown by Calendar.getTime()
        // if any fields are out of range, e.g., MONTH == 17.
        catch (IllegalArgumentException e) {
            pos.errorIndex = start;
            pos.index = oldStart;
            return null;
        }

其中,calb.establish(calendar)初始化了calendar日曆資料

跟進檢視

        //1-----------------------清空
        cal.clear();
        // Set the fields from the min stamp to the max stamp so that
        // the field resolution works in the Calendar.
        for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
            for (int index = 0; index <= maxFieldIndex; index++) {
                if (field[index] == stamp) {
                    cal.set(index, field[MAX_FIELD + index]);//2--------------設定
                    break;
                }
            }
        }

        if (weekDate) {
            int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1;
            int dayOfWeek = isSet(DAY_OF_WEEK) ?
                                field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
            if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) {
                if (dayOfWeek >= 8) {
                    dayOfWeek--;
                    weekOfYear += dayOfWeek / 7;
                    dayOfWeek = (dayOfWeek % 7) + 1;
                } else {
                    while (dayOfWeek <= 0) {
                        dayOfWeek += 7;
                        weekOfYear--;
                    }
                }
                dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
            }
            cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek);//2-----------設定
        }

        //3-------------------返回
        return cal;

參考1- 2- 3-註釋,執行緒不安全的原因,不能保證多核模式下,多執行緒執行是順序執行的原子性

2. format方法原始碼

/**
     * Formats the given <code>Date</code> into a date/time string and appends
     * the result to the given <code>StringBuffer</code>.
     *
     * @param date the date-time value to be formatted into a date-time string.
     * @param toAppendTo where the new date-time text is to be appended.
     * @param pos the formatting position. On input: an alignment field,
     * if desired. On output: the offsets of the alignment field.
     * @return the formatted date-time string.
     * @exception NullPointerException if the given {@code date} is {@code null}.
     */
    @Override
    public StringBuffer format(Date date, StringBuffer toAppendTo,
                               FieldPosition pos)
    {
        pos.beginIndex = pos.endIndex = 0;
        return format(date, toAppendTo, pos.getFieldDelegate());
    }

    // Called from Format after creating a FieldDelegate
    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);

        boolean useDateFormatSymbols = useDateFormatSymbols();

        for (int i = 0; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> 8;
            int count = compiledPattern[i++] & 0xff;
            if (count == 255) {
                count = compiledPattern[i++] << 16;
                count |= compiledPattern[i++];
            }

            switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char)count);
                break;

            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;

            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break;
            }
        }
        return toAppendTo;
    }

//設定日期

calendar.setTime(date); 

//格式化時間

subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);

calendar是有狀態的,不能保證原子性。

3. 解決方法

3.1. 加鎖

自建方法包裹程式碼


public class SimpleDateFormatTest {

    
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static synchronized Date parse(String s) throws ParseException {
        return sdf.parse(s);
    }

    public static void main(String[] args) {
        
        for (int i = 0; i <20 ; ++i) {
            Thread thread = new Thread(() -> {
                public void run() {
                    try {
                        
                            System.out.println(parse("2018-07-17 10:52:16"));
                        
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread.start()
        }
    }

}

序列執行,在大批量呼叫效率很低,不推薦

3.2.每次呼叫建立一個物件

    public Date parse(String s) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(s);
    }

缺點,耗記憶體,GC壓力增大,每次建立物件銷燬,分配和銷燬記憶體空間,整理記憶體,GC。不推薦

3.3.每個執行緒建立一個物件

public class SimpleDateFormatTest {
    
    static ThreadLocal<SimpleDateFormat> local= new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue(){
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static void main(String[] args) {
        
        for (int i = 0; i < 5; ++i) {
            Thread thread = new Thread(() -> {
                public void run() {
                    try {
                        System.out.println(local.get().parse("2018-07-17 11:01:16"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }finally {
                        //TODO 執行緒執行結束清除,避免記憶體洩露
                        local.remove();
                    }
                }
            });

            thread.start();
        }
    }

}

4. 總結 

推薦使用ThreadLocal,每個執行緒一個物件,提高format物件利用率

注意:使用ThreadLocal的Thread均持有ThreadLocal自定義的map物件,key是ThreadLocal物件的弱引用,隨ThreadLocal的GC或者記憶體不足就會GC,value是我們建立的物件是強引用,可能一直存在。因此ThreadLocal呼叫結束後,需要顯示呼叫remove()方法

相關推薦

執行SimpleDateFormat日期格式化ThreadLocal用法

前言 SimpleDateFormat 是 JDK 提供的一個日期格式化和解析類,但它是非執行緒安全的,原因如下。 1. parse方法 Date parsedDate; try { parsedDate = ca

Java執行程式設計-(9)-ThreadLocal造成OOM記憶體溢位案例演示原理分析

原文出自 : https://blog.csdn.net/xlgen157387/article/details/78298840 案例程式碼 1、首先看一下程式碼,模擬了一個執行緒數為500的執行緒池,所有執行緒共享一個ThreadLocal變數,每一個執行緒執

執行(八): VectorArrayList

ArrayList不允許寫操作沒執行完就執行讀操作,正在讀的時候不允許去寫,必須寫完再讀,讀完再寫。 一:ArrayList不安全示例 使用ArrayList每次列印的集合數量可能會小於10000,而使用Vector每次都是10000 public class ListTes

執行基礎4 同步通訊

 1.什麼情況下需要同步 當多執行緒併發執行同一程式碼時 希望某一段程式碼執行的過程中CPU不要切換到其他執行緒工作. 這時就需要同步.   2.同步程式碼塊 使用synchronized關鍵字加上一個鎖物件來定義一段程式碼, 這就叫同步程式碼塊 多個同步程式碼塊

執行程式設計之RunnableCallable區別

Runnable @FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used

執行】Thread.interrupted()thread.isInterrupted()的區別

在Java的執行緒基本操作方法中,有兩種方式獲取當前執行緒的isInterrupt屬性。一種是物件方法thread.isInterrupted(),另一種是Thread類的靜態方法Thread.interrupted()。這兩個方法看似相同,實際上是有區別的,我們來看看Jav

Java執行2.4.生產者消費者之間的關係3

生產者與消費者之間的關係 1、執行緒間通訊舉例的問題解決2 (1)建立學生類 package cn.itcast_05; public class Student { String name; int age; boolean flag; // 預設情況是沒有

執行學習之生產者消費者

感悟:   生產者和消費者其實就是一個很簡單的一個多執行緒同步的例子,但是我卻花了好多天才能理解。反思下來就是缺乏實踐,這聽起來感覺很簡單的一個道理,但是能做到可就很難了。當初在看這個生產者和消費者例項的時候,看著感覺程式碼能簡單,大致上能理解,但是到了轉天自己卻又寫不出來了

C++ 11 執行下std::unique_lockstd::lock_guard的區別和用法

這裡主要介紹std::unique_lock與std::lock_guard的區別用法 先說簡單的 一、std::lock_guard的用法 std::lock_guard其實就是簡單的RAII封裝,在建構函式中進行加鎖,解構函式中進行解鎖,這樣可以保證函式退出時,鎖一定被釋放。 簡單來說,就是防止開

Android執行-----併發和同步(ThreadLocal

一.對ThreadLocal的理解        很多地方叫做執行緒本地變數,也有些地方叫做執行緒本地儲存,其實意思差不多。可能很多朋友都知道ThreadLocal為變數在每個執行緒中都建立了一個副本,那麼每個執行緒可以訪問自己內部的副本變數,也就是進行資

執行的基本概念執行安全問題

多執行緒: 基本概念 程式:是一個可執行的檔案. 程序:是一個正在執行的程式.在記憶體中開闢了一塊兒空間 執行緒:負責程式的執行,可以看做程式執行的一條通道或者一個執行單元.所以我們通常將

執行之:SynchronizedReentrantLock

什麼是執行緒安全 保證多執行緒環境下共享的、可修改的狀態的正確性。(這裡的狀態在程式中可以看作為資料) 反著來說則是如果狀態非共享、不可修改,也就不存線上程安全的問題 保證執行緒安全的兩種方法 封裝,通過封裝將物件內部狀態隱藏、保護起來 不可變,將狀態改為

java執行通訊(伺服器客戶端)

基於TCP的多執行緒通訊 伺服器執行緒: package com.netproject1; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOExc

Python學習【第24篇】:死鎖,遞迴鎖,訊號量,Event事件,執行Queue python併發程式設計之執行2------------死鎖遞迴鎖,訊號量等

python併發程式設計之多執行緒2------------死鎖與遞迴鎖,訊號量等 一、死鎖現象與遞迴鎖 程序也是有死鎖的 所謂死鎖: 是指兩個或兩個以上

Python執行中join函式setDaemon函式使用說明

      在Python多執行緒程式設計的時候,經常需要用到join函式和setDaemon函式。之前對這兩個函式一直理解不是很到位。今天查閱了很多資料,對兩個函式的認識更加的深入一些了。       join([timeout])可以參考Python文件說明。大概意思就

非同步程式設計學習之路(三)-執行之間的協作通訊

本文是非同步程式設計學習之路(三)-多執行緒之間的協作與通訊,若要關注前文,請點選傳送門: 非同步程式設計學習之路(二)-通過Synchronize實現執行緒安全的多執行緒 通過前文,我們學習到如何實現同步的多執行緒,但是在很多情況下,僅僅同步是不夠的,還需要執行緒與執行緒協作(通訊),生產

Java執行之Callable介面Runnable的實現以及選擇

通過實現Runnable介面的實現 package Thread; import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors; public class RunnableThreadDemo { pr

基於socket和執行的聊天程式設計實現

【要求】 1. 設計一款多人聊天程式,包括伺服器端和客戶端; 2. 伺服器先執行,自動獲取IP,建立socket並繫結在2017埠; 3. 客戶端通過IP地址連線伺服器端,由argv[1]提供IP地址,回車後提示輸入暱稱,然後登入伺服器; 4. 

執行三(執行資料安全問題三種解決方式)

      最近在做與下載相關的APK的時候,需要用到多執行緒的一些知識,之前用的不是很多很深入,所以現在重新翻出來學習並且記錄一下,這部分內容目前準備三個階段完成;第一部分是一些基本概念與多執行緒幾種常見的實現方式;第二部分是執行緒相關的一些方法以及使用過程中的一些注意事項

Java執行初探——yield()方法join()方法

一、執行緒與程序 1、程序是程式(任務)執行過程,持有資源(共享記憶體,共享檔案)和執行緒,程序是動態性的,如果程式沒有執行就不算一個程序。 2、執行緒是系統中最小的執行單元,同一程序中有多個執行緒,執行緒共享程序的資源 Java中建立現成的方式就不再贅述了,有兩種:(1