SimpleDateFormat與DecimalFormat的并發(fā)安全

java在做日期轉(zhuǎn)換時(shí)我們會(huì)使用SimpleDateFormat做時(shí)間轉(zhuǎn)換焰檩,但其實(shí)SimpleDateFormat不是線程安全的,如果SimpleDateFormat用static聲明或只實(shí)例化一次被多個(gè)線程使用,并發(fā)度高時(shí)就會(huì)出并發(fā)異常,看如下例子

public static void main(String[] args) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        List<String> lists = new ArrayList<String>() {
            {
                add("2018-11-22 01:11:11");
                add("2018-11-22 02:22:22");
                add("2018-11-22 03:33:33");
                add("2018-11-22 04:44:44");
                add("2018-11-22 03:55:55");
                add("2018-11-22 04:55:56");
            }
        };
        ExecutorService executorService = Executors.newCachedThreadPool();
        //用CountDownLatch增加并發(fā)度
        CountDownLatch countDownLatch = new CountDownLatch(lists.size());
        for (String list : lists) {
            executorService.submit(() -> {
                try {
                    countDownLatch.await();
                    Date parse = format.parse(list);
                    System.out.println(parse.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            countDownLatch.countDown();
        }


    }

運(yùn)行日志如下诬滩,可能每次報(bào)錯(cuò)不一樣

java.lang.NumberFormatException: multiple points
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at thread.aqs.SimpleDateFormateTest.lambda$main$0(SimpleDateFormateTest.java:42)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: For input string: "E2E2."
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at thread.aqs.SimpleDateFormateTest.lambda$main$0(SimpleDateFormateTest.java:42)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: For input string: "E2E"
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at thread.aqs.SimpleDateFormateTest.lambda$main$0(SimpleDateFormateTest.java:42)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: For input string: "E.4220118E4"

或者日期錯(cuò)亂的結(jié)果

Thu Nov 22 04:55:56 CST 2018
Sat Nov 30 02:22:22 CST 2024
Tue Nov 30 01:11:11 CST 1
Thu Nov 22 04:55:56 CST 2018

原因

有兩個(gè)地方存在并發(fā)問(wèn)題

1、Calendar的共享

SimpleDateFormat的父類(lèi)持有一個(gè)對(duì)象Calendar
parse方法流程是這樣的:

//解析字符串
...
//清空Calendar
Calendar.clear();
//循環(huán)設(shè)置每個(gè)field
...

所以在多線程中會(huì)出現(xiàn)A線程設(shè)置Calendar值后被B線程修改灭将,
或者A和B線程同時(shí)設(shè)置Calendar的值疼鸟,都會(huì)出現(xiàn)錯(cuò)誤

看一下源碼

SimpleDateFormat繼承DateFormat

public class SimpleDateFormat extends DateFormat
入口
    public Date parse(String source) throws ParseException
    {
        ParsePosition pos = new ParsePosition(0);
        Date result = parse(source, pos);
        if (pos.index == 0)
            throw new ParseException("Unparseable date: \"" + source + "\"" ,
                pos.errorIndex);
        return result;
    }
parse中解析字符串后的一部分
        Date parsedDate;
        try {
            //出問(wèn)題的方法
            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 establish(Calendar cal) {
        boolean weekDate = isSet(WEEK_YEAR)
                            && field[WEEK_YEAR] > field[YEAR];
        if (weekDate && !cal.isWeekDateSupported()) {
            // Use YEAR instead
            if (!isSet(YEAR)) {
                set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
            }
            weekDate = false;
        }
        //清空
        cal.clear();
        for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
            for (int index = 0; index <= maxFieldIndex; index++) {
                if (field[index] == stamp) {
                    //設(shè)置field
                    cal.set(index, field[MAX_FIELD + index]);
                    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);
        }
        return cal;
    }

2、DecimalFormat對(duì)象不是線程安全的

SimpleDateFormat持有對(duì)象DecimalFormat庙曙,而DecimalFormat也不是線程安全的空镜,所以會(huì)報(bào)上面的那些錯(cuò)

parse方法

DecimalFormat對(duì)象持有一個(gè)DigitList對(duì)象,用來(lái)標(biāo)記解析狀態(tài)

private transient DigitList digitList = new DigitList();
    @Override
    public Number parse(String text, ParsePosition pos) {
        // special case NaN
        if (text.regionMatches(pos.index, symbols.getNaN(), 0, symbols.getNaN().length())) {
            pos.index = pos.index + symbols.getNaN().length();
            return new Double(Double.NaN);
        }

        boolean[] status = new boolean[STATUS_LENGTH];
        //這里subparse方法清空和組裝digitList,多線程并發(fā)會(huì)出問(wèn)題
        if (!subparse(text, pos, positivePrefix, negativePrefix, digitList, false, status)) {
            return null;
        }
        ...

如何解決

每次使用時(shí)都new一個(gè)SimpleDateFormat當(dāng)然可以解決這個(gè)問(wèn)題捌朴,但頻繁創(chuàng)建銷(xiāo)毀對(duì)象效能不高吴攒,方法上加鎖又會(huì)降低并發(fā)度

因?yàn)槊總€(gè)線程自己執(zhí)行肯定是按順序執(zhí)行,所以可以利用ThreadLocal

    public class DateUtil  {

    private static ThreadLocal<DateFormat> threadLocal = ThreadLocal.withInitial(()->{
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    });

    public static Date parse(String dateStr) throws ParseException {
        return threadLocal.get().parse(dateStr);
    }

    public static String format(Date date) {
        return threadLocal.get().format(date);
    }

}

每個(gè)線程持有自己的SimpleDateFormat砂蔽,再跑測(cè)試代碼洼怔,發(fā)現(xiàn)不報(bào)錯(cuò)了

//輸出
Thu Nov 22 01:11:11 CST 2018
Thu Nov 22 04:55:56 CST 2018
Thu Nov 22 03:33:33 CST 2018
Thu Nov 22 03:55:55 CST 2018
Thu Nov 22 02:22:22 CST 2018
Thu Nov 22 04:44:44 CST 2018

使用jodatime

      String date = "2018-11-22 02:22:22";
      DateTime dateTime =  DateTime.parse(date, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));

或者使用java8的LocalDateTime

      String str = "1986-04-08 12:30:22";
      DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
      LocalDateTime dateTime = LocalDateTime.parse(str, formatter);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市左驾,隨后出現(xiàn)的幾起案子镣隶,更是在濱河造成了極大的恐慌极谊,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件安岂,死亡現(xiàn)場(chǎng)離奇詭異轻猖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)域那,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)咙边,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人次员,你說(shuō)我怎么就攤上這事败许。” “怎么了翠肘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵檐束,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我束倍,道長(zhǎng)被丧,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任绪妹,我火速辦了婚禮甥桂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘邮旷。我一直安慰自己黄选,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布婶肩。 她就那樣靜靜地躺著办陷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪律歼。 梳的紋絲不亂的頭發(fā)上民镜,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音险毁,去河邊找鬼制圈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛畔况,可吹牛的內(nèi)容都是我干的鲸鹦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼跷跪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼馋嗜!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起域庇,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嵌戈,失蹤者是張志新(化名)和其女友劉穎覆积,沒(méi)想到半個(gè)月后听皿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體熟呛,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年尉姨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了庵朝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡又厉,死狀恐怖九府,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情覆致,我是刑警寧澤侄旬,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站煌妈,受9級(jí)特大地震影響儡羔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜璧诵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一汰蜘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧之宿,春花似錦族操、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至等缀,卻和暖如春枷莉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背项滑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工依沮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枪狂。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓危喉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親州疾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辜限,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 想必大家對(duì)SimpleDateFormat并不陌生。SimpleDateFormat 是 Java 中一個(gè)非常常用...
    可愛(ài)傻妞是我的愛(ài)閱讀 1,214評(píng)論 0 4
  • 我答應(yīng)你的 今年春節(jié) 我就來(lái) 可我撥打你的電話(huà) 象往常那樣 你沒(méi)有接 正月的月圓了又缺 我給你說(shuō)過(guò) 我严蓖、將會(huì)來(lái) 可...
    本無(wú)痕閱讀 313評(píng)論 6 4
  • 夜晚薄嫡,迎著落日余暉 躺在床上 細(xì)細(xì)摩挲著白日的紋理 與一日的憂(yōu)愁 仔細(xì)斟酌 時(shí)光如梭 流出的是回憶 剩下的是殘痕氧急。...
    后院豌豆閱讀 191評(píng)論 0 1
  • 申請(qǐng)助推篇:傳燈,這首歌給的靈感毫深。 緣起:2017年11月11日吩坝,因新項(xiàng)目團(tuán)隊(duì)管理出現(xiàn)問(wèn)題,帶著解決問(wèn)題的心態(tài)走進(jìn)...
    子君_Mery閱讀 395評(píng)論 0 1
  • 黃昏,燃燒成灰燼 夜色開(kāi)始籠罩了人間 城市的某一個(gè)角落點(diǎn)亮了一盞燈 緊接著闸迷,許多盞燈都亮了 燈火闌珊嵌纲,寂靜在這一刻...
    清杉_61fa閱讀 278評(píng)論 0 0