最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SimpleDateFormat在多線程下的安全問(wèn)題及解決

 更新時(shí)間:2026年03月07日 14:58:59   作者:zmbwcx2003  
SimpleDateFormat類在Java中用于日期時(shí)間的格式化和解析,但在高并發(fā)環(huán)境下存在線程不安全的問(wèn)題,原因是它共享了一個(gè)Calendar對(duì)象,導(dǎo)致多個(gè)線程操作時(shí)出現(xiàn)沖突,為了解決這個(gè)問(wèn)題,可以使用局部變量、加鎖或使用線程安全的DateTimeFormatter類

情景重現(xiàn)

SimpleDateFormat類是Java開發(fā)中的一個(gè)日期時(shí)間的轉(zhuǎn)化類。它可以滿足絕大多數(shù)的開發(fā)場(chǎng)景,但是在高并發(fā)下會(huì)出現(xiàn)并發(fā)問(wèn)題。

接下來(lái)查看下文中的案例。

public class TestSimpleDateFormat {
    public static void main(String[] args) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    Date parse = format.parse("2003-01-01");
                    System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

上面代碼簡(jiǎn)單來(lái)說(shuō)就是創(chuàng)建了一個(gè)SimpleDateFormat類對(duì)象,該對(duì)象被后續(xù)會(huì)被五個(gè)線程使用,去轉(zhuǎn)化日期格式并打印。

我們來(lái)查看輸出結(jié)果:

java.lang.NumberFormatException: empty String
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
    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 test.lambda$main$0(test.java:21)
    at java.lang.Thread.run(Thread.java:745)
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 test.lambda$main$0(test.java:21)
    at java.lang.Thread.run(Thread.java:745)
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 test.lambda$main$0(test.java:21)
    at java.lang.Thread.run(Thread.java:745)
java.lang.NumberFormatException: empty String
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
    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 test.lambda$main$0(test.java:21)
    at java.lang.Thread.run(Thread.java:745)
Fri Nov 01 00:00:00 CST 2222

可以看到,只輸出了一次時(shí)間轉(zhuǎn)化,并且該輸出格式還是錯(cuò)誤的。

接下來(lái)我們來(lái)查看為什么SimpleDateFormat類是線程不安全的。

SimpleDateFormat解析

我們根據(jù)parse()方法,查看SimpleDateFormat是如何進(jìn)行格式轉(zhuǎn)換的。

我們可以看到,返回結(jié)果是根據(jù)另一個(gè)方法獲取到的,接下來(lái)我們接著查看該parse()源碼。

這是一個(gè)抽象方法,接著我們?nèi)ゲ榭此木唧w實(shí)現(xiàn)。

可以看到該方法很長(zhǎng),但是我們只關(guān)注返回如何結(jié)果,直接拉到最后查看該方法如何返回一個(gè)日期格式 。上圖中,最后一次修改parseDate對(duì)象是在箭頭的位置。那么我們查看getTime()方法。

可以看到該方法是由Calendar類提供的,該類名翻譯為中文就是日歷的意思,并且返回結(jié)果也是我們需要的日期格式,那么我們就可以確定該方法用于給parseDate對(duì)象提供返回值的,接下來(lái)回退一下查看其他方法哪個(gè)是提供Calendar對(duì)象來(lái)調(diào)用getTime()方法的。

現(xiàn)在我們清楚了Calendar類對(duì)象是由establish()方法提供的了,該方法中需要一個(gè)參數(shù)calendar對(duì)象。該對(duì)象由SimpleDateFormat的父類DateFormat來(lái)維護(hù)。

此時(shí)我們或許大概明白是因?yàn)镾impleDateFormat類之所以線程不安全的問(wèn)題是因?yàn)樵诙嗑€程下共享了calendar對(duì)象。接下來(lái)我們繼續(xù)查看establish()方法,驗(yàn)證是否是這樣,下面是具體源碼

    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();
        // 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]);
                    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;
    }

可以看到,在該方法中,對(duì)cal對(duì)象執(zhí)行了clear()方法與set()方法,我們查看clear方法是做什么的

該方法提供了類似初始化的功能,將上一次的格式轉(zhuǎn)化保存的cal屬性清除。

而set()方法將本次的格式轉(zhuǎn)換需要的數(shù)據(jù)更新。因此,我們可以確定了SimpleDateFormat類之所以線程不安全就是因?yàn)楣蚕砹薱alendar對(duì)象。

解決方案

為了避免SimpleDateFormat格式轉(zhuǎn)換帶來(lái)的并發(fā)問(wèn)題,我們可以采取以下幾個(gè)措施

局部變量

我們已經(jīng)知道了產(chǎn)生線程安全問(wèn)題的原因是共享了相同屬性,那么我們只要讓每個(gè)線程都包含自己的屬性就可以避免該問(wèn)題的發(fā)生。

具體實(shí)現(xiàn)代碼如下:

public class TestSimpleDateFormat {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
        new Thread(()->{
            try {
                    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                    Date parse = format.parse("2003-01-01");
                    System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

運(yùn)行結(jié)果如下:

Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003

這種方式不太推薦,因?yàn)闀?huì)創(chuàng)建大量的SimpleDateFormat對(duì)象,占用內(nèi)存空間。 

加鎖

除了讓每個(gè)線程都擁有自己獨(dú)立的對(duì)象外,我們也可以保證在同一時(shí)刻下,只有一個(gè)線程對(duì)共享屬性進(jìn)行修改,那就是加鎖。具體實(shí)現(xiàn)代碼如下

public class TestSimpleDateFormat{
    public static void main(String[] args) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    Date parse;
                    synchronized (format){
                        parse = format.parse("2003-01-01");
                    }
                    System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

但是這種方法不太推薦,因?yàn)槟軌虺霈F(xiàn)格式轉(zhuǎn)化錯(cuò)誤的情況已經(jīng)是很大的并發(fā)了,如果還使用同步鎖的話會(huì)影響性能。

使用線程變量

public class test {
    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    Date parse = threadLocal.get().parse("2023-01-01");
                    System.out.println(parse);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

使用DateTimeFormatter

在JDK8之后提供了線程安全的格式轉(zhuǎn)化DateTimeFormatter類,使用方法如下

public class test {
    public static void main(String[] args) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    TemporalAccessor parse = dateTimeFormatter.parse("2003-06-03");
                        System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

輸出結(jié)果為:

{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • java中判斷字符串?dāng)?shù)組是否包含特定字符串的三種方法實(shí)現(xiàn)與對(duì)比

    java中判斷字符串?dāng)?shù)組是否包含特定字符串的三種方法實(shí)現(xiàn)與對(duì)比

    這篇文章主要為大家詳細(xì)介紹了java中判斷字符串?dāng)?shù)組是否包含特定字符串的三種方法實(shí)現(xiàn)與對(duì)比,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下
    2025-12-12
  • Java?導(dǎo)出Excel增加下拉框選項(xiàng)

    Java?導(dǎo)出Excel增加下拉框選項(xiàng)

    這篇文章主要介紹了Java?導(dǎo)出Excel增加下拉框選項(xiàng),excel對(duì)于下拉框較多選項(xiàng)的,需要使用隱藏工作簿來(lái)解決,使用函數(shù)取值來(lái)做選項(xiàng),下文具體的操作詳情,需要的小伙伴可以參考一下
    2022-04-04
  • spring boot使用thymeleaf為模板的基本步驟介紹

    spring boot使用thymeleaf為模板的基本步驟介紹

    Spring Boot項(xiàng)目的默認(rèn)模板引擎是Thymeleaf,這沒什么好說(shuō)的,個(gè)人覺得也非常好,下面這篇文章主要給大家介紹了關(guān)于spring boot使用thymeleaf為模板的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-01-01
  • Java Socket聊天室編程(二)之利用socket實(shí)現(xiàn)單聊聊天室

    Java Socket聊天室編程(二)之利用socket實(shí)現(xiàn)單聊聊天室

    這篇文章主要介紹了Java Socket聊天室編程(二)之利用socket實(shí)現(xiàn)單聊聊天室的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-09-09
  • 使用Jmeter進(jìn)行http接口測(cè)試的詳細(xì)流程

    使用Jmeter進(jìn)行http接口測(cè)試的詳細(xì)流程

    本文主要針對(duì)http接口進(jìn)行測(cè)試,使用Jmeter工具實(shí)現(xiàn),  Jmter工具設(shè)計(jì)之初是用于做性能測(cè)試的,它在實(shí)現(xiàn)對(duì)各種接口的調(diào)用方面已經(jīng)做的比較成熟,因此,本次直接使用Jmeter工具來(lái)完成對(duì)Http接口的測(cè)試,需要的朋友可以參考下
    2024-12-12
  • SpringBoot中自動(dòng)配置原理解析

    SpringBoot中自動(dòng)配置原理解析

    SpringBoost是基于Spring框架開發(fā)出來(lái)的功能更強(qiáng)大的Java程序開發(fā)框架,本文將以廣角視覺來(lái)剖析SpringBoot自動(dòng)配置的原理,涉及部分Spring、SpringBoot源碼,需要的可以參考下
    2023-11-11
  • java中ThreadLocal和ThreadLocalMap淺析

    java中ThreadLocal和ThreadLocalMap淺析

    這篇文章主要介紹了java中ThreadLocal和ThreadLocalMap淺析,ThreadLocal類用來(lái)設(shè)置線程私有變量?本身不儲(chǔ)存值?主要提供自身引用?和?操作ThreadLocalMap?屬性值得方法,需要的朋友可以參考下
    2023-09-09
  • Springboot轉(zhuǎn)發(fā)重定向?qū)崿F(xiàn)方式解析

    Springboot轉(zhuǎn)發(fā)重定向?qū)崿F(xiàn)方式解析

    這篇文章主要介紹了springboot轉(zhuǎn)發(fā)重定向?qū)崿F(xiàn)方式解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Java經(jīng)典面試題匯總:網(wǎng)絡(luò)編程

    Java經(jīng)典面試題匯總:網(wǎng)絡(luò)編程

    本篇總結(jié)的是Java 網(wǎng)絡(luò)編程相關(guān)的面試題,后續(xù)會(huì)持續(xù)更新,希望我的分享可以幫助到正在備戰(zhàn)面試的實(shí)習(xí)生或者已經(jīng)工作的同行,如果發(fā)現(xiàn)錯(cuò)誤還望大家多多包涵,不吝賜教,謝謝
    2021-07-07
  • 解讀java.lang.Character.isLetterOrDigit()的使用方式

    解讀java.lang.Character.isLetterOrDigit()的使用方式

    這篇文章主要介紹了解讀java.lang.Character.isLetterOrDigit()的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06

最新評(píng)論

来宾市| 缙云县| 临城县| 昭平县| 灌阳县| 若尔盖县| 东港市| 准格尔旗| 手机| 皋兰县| 莫力| 沽源县| 会宁县| 井陉县| 西畴县| 禄丰县| 鄄城县| 淮安市| 称多县| 思南县| 广饶县| 安多县| 遂溪县| 辽宁省| 南雄市| 娱乐| 丹江口市| 景东| 衡阳市| 临西县| 荥阳市| 辛集市| 建阳市| 陵水| 梅河口市| 疏附县| 新巴尔虎左旗| 岳阳县| 铜川市| 运城市| 上虞市|